From b76245dc54b0dee199c786ba43d92a6847defbbc Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 11 Aug 2025 19:50:07 +0000 Subject: [PATCH 01/14] Deserialize error details --- databricks-sdk-java/pom.xml | 6 + .../databricks/sdk/core/DatabricksError.java | 5 +- .../sdk/core/error/ApiErrorBody.java | 17 +- .../sdk/core/error/details/BadRequest.java | 186 ++++++++ .../sdk/core/error/details/DebugInfo.java | 113 +++++ .../sdk/core/error/details/ErrorDetails.java | 53 +++ .../details/ErrorDetailsDeserializer.java | 180 ++++++++ .../core/error/details/ErrorDetailsTest.java | 1 + .../sdk/core/error/details/ErrorInfo.java | 117 +++++ .../sdk/core/error/details/Help.java | 201 +++++++++ .../error/details/PreconditionFailure.java | 202 +++++++++ .../sdk/core/error/details/QuotaFailure.java | 169 ++++++++ .../sdk/core/error/details/RequestInfo.java | 100 +++++ .../sdk/core/error/details/ResourceInfo.java | 189 ++++++++ .../sdk/core/error/details/RetryInfo.java | 130 ++++++ .../databricks/sdk/core/utils/SerDeUtils.java | 2 + .../databricks/sdk/core/ApiClientTest.java | 31 +- .../core/error/details/ErrorDetailsTest.java | 402 ++++++++++++++++++ 18 files changed, 2089 insertions(+), 15 deletions(-) create mode 100644 databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/BadRequest.java create mode 100644 databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/DebugInfo.java create mode 100644 databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java create mode 100644 databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetailsDeserializer.java create mode 100644 databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java create mode 100644 databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorInfo.java create mode 100644 databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/Help.java create mode 100644 databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/PreconditionFailure.java create mode 100644 databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/QuotaFailure.java create mode 100644 databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RequestInfo.java create mode 100644 databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ResourceInfo.java create mode 100644 databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RetryInfo.java create mode 100644 databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java diff --git a/databricks-sdk-java/pom.xml b/databricks-sdk-java/pom.xml index f456d1f10..7ad1e0ab0 100644 --- a/databricks-sdk-java/pom.xml +++ b/databricks-sdk-java/pom.xml @@ -104,6 +104,12 @@ jackson-datatype-jsr310 ${jackson.version} + + + com.fasterxml.jackson.datatype + jackson-datatype-guava + ${jackson.version} + com.google.auto.value diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksError.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksError.java index 6538b6719..8e072b278 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksError.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksError.java @@ -14,8 +14,6 @@ * unrecoverable way and this exception should be thrown, potentially wrapped in another exception. */ public class DatabricksError extends DatabricksException { - private static final String ERROR_INFO_TYPE = "type.googleapis.com/google.rpc.ErrorInfo"; - private final String message; private final Throwable cause; private final String errorCode; private final int statusCode; @@ -51,14 +49,13 @@ private DatabricksError( List details) { super(message, cause); this.errorCode = errorCode; - this.message = message; this.cause = cause; this.statusCode = statusCode; this.details = details == null ? Collections.emptyList() : details; } public List getErrorInfo() { - return this.getDetailsByType(ERROR_INFO_TYPE); + return this.getDetailsByType("type.googleapis.com/google.rpc.ErrorInfo"); } public String getErrorCode() { 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 4c7e386da..ae2e1397c 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 @@ -2,7 +2,10 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.databricks.sdk.core.error.details.ErrorDetails; import java.util.List; +import java.util.Arrays; +import java.util.Collections; /** * The union of all JSON error responses from the Databricks APIs, not including HTML responses. @@ -28,14 +31,14 @@ public ApiErrorBody( @JsonProperty("status") String scimStatus, @JsonProperty("scimType") String scimType, @JsonProperty("error") String api12Error, - @JsonProperty("details") List errorDetails) { + @JsonProperty("details") ErrorDetails errorDetails) { this.errorCode = errorCode; this.message = message; this.scimDetail = scimDetail; this.scimStatus = scimStatus; this.scimType = scimType; this.api12Error = api12Error; - this.errorDetails = errorDetails; + this.errorDetails = fromDetails(errorDetails); } public List getErrorDetails() { @@ -93,4 +96,14 @@ public String getApi12Error() { public void setApi12Error(String api12Error) { this.api12Error = api12Error; } + + private static List fromDetails(ErrorDetails details) { + if (details == null) { + return Collections.emptyList(); + } + if (!details.errorInfo().isPresent()) { + return Collections.emptyList(); + } + return Arrays.asList(new ErrorDetail("type.googleapis.com/google.rpc.ErrorInfo", details.errorInfo().get().reason(), details.errorInfo().get().domain(), details.errorInfo().get().metadata())); + } } diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/BadRequest.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/BadRequest.java new file mode 100644 index 000000000..ec1024605 --- /dev/null +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/BadRequest.java @@ -0,0 +1,186 @@ +package com.databricks.sdk.core.error.details; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import java.util.List; + +/** + * BadRequest describes violations in a client request. This error type focuses + * on the syntactic aspects of the request. + * + *

BadRequest errors occur when the request format, structure, or content + * does not meet the service's requirements. This is different from business + * logic errors or system failures - it specifically indicates that the client + * sent a malformed or invalid request. + * + *

Examples of bad request violations might include: + *

    + *
  • Missing required fields
  • + *
  • Invalid field values (wrong type, format, or range)
  • + *
  • Malformed JSON or XML
  • + *
  • Unsupported field combinations
  • + *
  • Invalid enum values
  • + *
  • Field length or size violations
  • + *
+ * + *

This information helps clients: + *

    + *
  • Identify what's wrong with their request
  • + *
  • Fix the request format before retrying
  • + *
  • Understand the service's input requirements
  • + *
  • Implement proper input validation
  • + *
+ */ +@AutoValue +@JsonDeserialize(builder = AutoValue_BadRequest.Builder.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class BadRequest { + + /** + * Describes all field violations in the request. + * + *

This list contains details about each specific field or aspect of + * the request that violated the service's requirements. Multiple violations + * can occur if the request has multiple problems. + * + * @return the list of field violations + */ + @JsonProperty("field_violations") + public abstract List fieldViolations(); + + /** + * Creates a new builder for constructing BadRequest instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_BadRequest.Builder(); + } + + /** + * Builder for constructing BadRequest instances. + */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + + /** + * Sets the field violations. + * + * @param fieldViolations the list of field violations + * @return this builder for method chaining + */ + @JsonProperty("field_violations") + public abstract Builder fieldViolations(List fieldViolations); + + /** + * Builds the BadRequest instance. + * + * @return a new BadRequest instance + */ + public abstract BadRequest build(); + } + + /** + * BadRequestFieldViolation describes a specific field violation in a request. + * + *

Each violation provides details about what specific field or aspect + * of the request was invalid and how the client can fix it. + */ + @AutoValue + @JsonDeserialize(builder = AutoValue_BadRequest_BadRequestFieldViolation.Builder.class) + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class BadRequestFieldViolation { + + /** + * A path leading to a field in the request body. + * + *

This field identifies the specific location of the violation + * within the request structure. The path format depends on the request + * format but typically follows a hierarchical structure. + * + *

Examples of field paths: + *

    + *
  • "name" - top-level field
  • + *
  • "user.email" - nested field
  • + *
  • "items[0].id" - array element field
  • + *
  • "metadata.api_key" - deeply nested field
  • + *
  • "settings.notifications.enabled" - multi-level nested field
  • + *
+ * + *

This path helps clients quickly locate and fix the problematic + * field in their request. + * + * @return the path to the violating field + */ + @JsonProperty("field") + public abstract String field(); + + /** + * A description of why the request element is bad. + * + *

This field provides a human-readable explanation of what's wrong + * with the field and how to fix it. The description should be clear + * enough for developers to understand and resolve the issue. + * + *

Examples of field violation descriptions: + *

    + *
  • "Field is required and cannot be empty"
  • + *
  • "Value must be a positive integer"
  • + *
  • "Invalid email format"
  • + *
  • "String length must be between 1 and 100 characters"
  • + *
  • "Unsupported enum value. Must be one of: [A, B, C]"
  • + *
  • "Field cannot contain special characters"
  • + *
  • "Date must be in ISO 8601 format (YYYY-MM-DD)"
  • + *
+ * + * @return description of why the field is invalid + */ + @JsonProperty("description") + public abstract String description(); + + /** + * Creates a new builder for constructing BadRequestFieldViolation instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_BadRequest_BadRequestFieldViolation.Builder(); + } + + /** + * Builder for constructing BadRequestFieldViolation instances. + */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + + /** + * Sets the field path. + * + * @param field the path to the violating field + * @return this builder for method chaining + */ + @JsonProperty("field") + public abstract Builder field(String field); + + /** + * Sets the violation description. + * + * @param description description of why the field is invalid + * @return this builder for method chaining + */ + @JsonProperty("description") + public abstract Builder description(String description); + + /** + * Builds the BadRequestFieldViolation instance. + * + * @return a new BadRequestFieldViolation instance + */ + public abstract BadRequestFieldViolation build(); + } + } +} \ No newline at end of file diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/DebugInfo.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/DebugInfo.java new file mode 100644 index 000000000..11cc10d13 --- /dev/null +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/DebugInfo.java @@ -0,0 +1,113 @@ +package com.databricks.sdk.core.error.details; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import java.util.List; + +/** + * DebugInfo describes additional debugging information. + * + *

This class provides detailed information that can be used by developers + * and support teams to understand what went wrong and where the error occurred. + * The information is typically more technical and detailed than what would be + * shown to end users. + * + *

DebugInfo is particularly useful for: + *

    + *
  • Development and testing environments
  • + *
  • Support ticket investigations
  • + *
  • System debugging and troubleshooting
  • + *
  • Understanding the root cause of errors
  • + *
+ */ +@AutoValue +@JsonDeserialize(builder = AutoValue_DebugInfo.Builder.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class DebugInfo { + + /** + * The stack trace entries indicating where the error occurred. + * + *

This list contains the call stack at the time the error occurred, + * typically starting from the most recent call and going backwards through + * the call chain. Each entry usually represents a method call or function + * invocation. + * + *

Stack trace information is invaluable for: + *

    + *
  • Identifying the exact location where an error occurred
  • + *
  • Understanding the execution path that led to the error
  • + *
  • Debugging complex error scenarios
  • + *
  • Providing context to support teams
  • + *
+ * + * @return the stack trace entries + */ + @JsonProperty("stack_entries") + public abstract List stackEntries(); + + /** + * Additional debugging information provided by the server. + * + *

This field can contain any additional context or details that the + * server deems useful for debugging purposes. The exact content depends + * on the service implementation and the specific error that occurred. + * + *

Examples of additional debugging information might include: + *

    + *
  • Internal error codes or identifiers
  • + *
  • System state information
  • + *
  • Configuration details
  • + *
  • Performance metrics
  • + *
  • Other diagnostic data
  • + *
+ * + * @return additional debugging information + */ + @JsonProperty("detail") + public abstract String detail(); + + /** + * Creates a new builder for constructing DebugInfo instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_DebugInfo.Builder(); + } + + /** + * Builder for constructing DebugInfo instances. + */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + + /** + * Sets the stack trace entries. + * + * @param stackEntries the stack trace entries + * @return this builder for method chaining + */ + @JsonProperty("stack_entries") + public abstract Builder stackEntries(List stackEntries); + + /** + * Sets the additional debugging information. + * + * @param detail additional debugging information + * @return this builder for method chaining + */ + @JsonProperty("detail") + public abstract Builder detail(String detail); + + /** + * Builds the DebugInfo instance. + * + * @return a new DebugInfo instance + */ + public abstract DebugInfo build(); + } +} \ No newline at end of file diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java new file mode 100644 index 000000000..808f181a0 --- /dev/null +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java @@ -0,0 +1,53 @@ +package com.databricks.sdk.core.error.details; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import java.util.List; +import java.util.Collections; +import java.util.Optional; + +@AutoValue +@JsonDeserialize(using = ErrorDetailsDeserializer.class) +public abstract class ErrorDetails { + + public abstract Optional errorInfo(); + + public abstract Optional requestInfo(); + + public abstract Optional retryInfo(); + + public abstract Optional debugInfo(); + + public abstract Optional quotaFailure(); + + public abstract Optional preconditionFailure(); + + public abstract Optional badRequest(); + + public abstract Optional resourceInfo(); + + public abstract Optional help(); + + public abstract List unknownDetails(); + + public static Builder builder() { + return new AutoValue_ErrorDetails.Builder() + .setUnknownDetails(Collections.emptyList()); + } + + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setErrorInfo(ErrorInfo errorInfo); + public abstract Builder setRequestInfo(RequestInfo requestInfo); + public abstract Builder setRetryInfo(RetryInfo retryInfo); + public abstract Builder setDebugInfo(DebugInfo debugInfo); + public abstract Builder setQuotaFailure(QuotaFailure quotaFailure); + public abstract Builder setPreconditionFailure(PreconditionFailure preconditionFailure); + public abstract Builder setBadRequest(BadRequest badRequest); + public abstract Builder setResourceInfo(ResourceInfo resourceInfo); + public abstract Builder setHelp(Help help); + public abstract Builder setUnknownDetails(List unknownDetails); + public abstract ErrorDetails build(); + } +} \ No newline at end of file diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetailsDeserializer.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetailsDeserializer.java new file mode 100644 index 000000000..ea5ec8989 --- /dev/null +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetailsDeserializer.java @@ -0,0 +1,180 @@ +package com.databricks.sdk.core.error.details; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class ErrorDetailsDeserializer extends JsonDeserializer { + + private static final String ERROR_INFO_TYPE = "type.googleapis.com/google.rpc.ErrorInfo"; + private static final String REQUEST_INFO_TYPE = "type.googleapis.com/google.rpc.RequestInfo"; + private static final String RETRY_INFO_TYPE = "type.googleapis.com/google.rpc.RetryInfo"; + private static final String DEBUG_INFO_TYPE = "type.googleapis.com/google.rpc.DebugInfo"; + private static final String QUOTA_FAILURE_TYPE = "type.googleapis.com/google.rpc.QuotaFailure"; + private static final String PRECONDITION_FAILURE_TYPE = "type.googleapis.com/google.rpc.PreconditionFailure"; + private static final String BAD_REQUEST_TYPE = "type.googleapis.com/google.rpc.BadRequest"; + private static final String RESOURCE_INFO_TYPE = "type.googleapis.com/google.rpc.ResourceInfo"; + private static final String HELP_TYPE = "type.googleapis.com/google.rpc.Help"; + + @Override + public ErrorDetails deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + ObjectMapper mapper = (ObjectMapper) p.getCodec(); + JsonNode node = mapper.readTree(p); + + if (node.isArray()) { + return deserializeFromArray((ArrayNode) node, mapper); + } else { + // Fallback to single object deserialization + return deserializeFromObject((ObjectNode) node, mapper); + } + } + + private ErrorDetails deserializeFromArray(ArrayNode arrayNode, ObjectMapper mapper) throws IOException { + ErrorDetails.Builder builder = ErrorDetails.builder(); + List unknownDetails = new ArrayList<>(); + + for (JsonNode detailNode : arrayNode) { + if (detailNode.isObject()) { + ObjectNode objectNode = (ObjectNode) detailNode; + String type = objectNode.path("@type").asText(); + + try { + switch (type) { + case ERROR_INFO_TYPE: + ErrorInfo errorInfo = mapper.treeToValue(objectNode, ErrorInfo.class); + builder.setErrorInfo(errorInfo); + break; + case REQUEST_INFO_TYPE: + RequestInfo requestInfo = mapper.treeToValue(objectNode, RequestInfo.class); + builder.setRequestInfo(requestInfo); + break; + case RETRY_INFO_TYPE: + RetryInfo retryInfo = mapper.treeToValue(objectNode, RetryInfo.class); + builder.setRetryInfo(retryInfo); + break; + case DEBUG_INFO_TYPE: + DebugInfo debugInfo = mapper.treeToValue(objectNode, DebugInfo.class); + builder.setDebugInfo(debugInfo); + break; + case QUOTA_FAILURE_TYPE: + QuotaFailure quotaFailure = mapper.treeToValue(objectNode, QuotaFailure.class); + builder.setQuotaFailure(quotaFailure); + break; + case PRECONDITION_FAILURE_TYPE: + PreconditionFailure preconditionFailure = mapper.treeToValue(objectNode, PreconditionFailure.class); + builder.setPreconditionFailure(preconditionFailure); + break; + case BAD_REQUEST_TYPE: + BadRequest badRequest = mapper.treeToValue(objectNode, BadRequest.class); + builder.setBadRequest(badRequest); + break; + case RESOURCE_INFO_TYPE: + ResourceInfo resourceInfo = mapper.treeToValue(objectNode, ResourceInfo.class); + builder.setResourceInfo(resourceInfo); + break; + case HELP_TYPE: + Help help = mapper.treeToValue(objectNode, Help.class); + builder.setHelp(help); + break; + default: + // Unknown type, add to unknownDetails + unknownDetails.add(detailNode); + break; + } + } catch (Exception e) { + // If deserialization fails for a known type, treat it as unknown + unknownDetails.add(detailNode); + } + } else { + // Non-object node, add to unknownDetails + unknownDetails.add(detailNode); + } + } + + builder.setUnknownDetails(unknownDetails); + return builder.build(); + } + + private ErrorDetails deserializeFromObject(ObjectNode objectNode, ObjectMapper mapper) throws IOException { + // Handle single object case - try to determine type and deserialize accordingly + String type = objectNode.path("@type").asText(); + + try { + switch (type) { + case ERROR_INFO_TYPE: + ErrorInfo errorInfo = mapper.treeToValue(objectNode, ErrorInfo.class); + return ErrorDetails.builder() + .setErrorInfo(errorInfo) + .setUnknownDetails(new ArrayList<>()) + .build(); + case REQUEST_INFO_TYPE: + RequestInfo requestInfo = mapper.treeToValue(objectNode, RequestInfo.class); + return ErrorDetails.builder() + .setRequestInfo(requestInfo) + .setUnknownDetails(new ArrayList<>()) + .build(); + case RETRY_INFO_TYPE: + RetryInfo retryInfo = mapper.treeToValue(objectNode, RetryInfo.class); + return ErrorDetails.builder() + .setRetryInfo(retryInfo) + .setUnknownDetails(new ArrayList<>()) + .build(); + case DEBUG_INFO_TYPE: + DebugInfo debugInfo = mapper.treeToValue(objectNode, DebugInfo.class); + return ErrorDetails.builder() + .setDebugInfo(debugInfo) + .setUnknownDetails(new ArrayList<>()) + .build(); + case QUOTA_FAILURE_TYPE: + QuotaFailure quotaFailure = mapper.treeToValue(objectNode, QuotaFailure.class); + return ErrorDetails.builder() + .setQuotaFailure(quotaFailure) + .setUnknownDetails(new ArrayList<>()) + .build(); + case PRECONDITION_FAILURE_TYPE: + PreconditionFailure preconditionFailure = mapper.treeToValue(objectNode, PreconditionFailure.class); + return ErrorDetails.builder() + .setPreconditionFailure(preconditionFailure) + .build(); + case BAD_REQUEST_TYPE: + BadRequest badRequest = mapper.treeToValue(objectNode, BadRequest.class); + return ErrorDetails.builder() + .setBadRequest(badRequest) + .build(); + case RESOURCE_INFO_TYPE: + ResourceInfo resourceInfo = mapper.treeToValue(objectNode, ResourceInfo.class); + return ErrorDetails.builder() + .setResourceInfo(resourceInfo) + .setUnknownDetails(new ArrayList<>()) + .build(); + case HELP_TYPE: + Help help = mapper.treeToValue(objectNode, Help.class); + return ErrorDetails.builder() + .setHelp(help) + .setUnknownDetails(new ArrayList<>()) + .build(); + default: + // Unknown type, treat as unknown detail + List unknownDetails = new ArrayList<>(); + unknownDetails.add(objectNode); + return ErrorDetails.builder() + .setUnknownDetails(unknownDetails) + .build(); + } + } catch (Exception e) { + // If deserialization fails, treat as unknown detail + List unknownDetails = new ArrayList<>(); + unknownDetails.add(objectNode); + return ErrorDetails.builder() + .setUnknownDetails(unknownDetails) + .build(); + } + } +} \ No newline at end of file diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java new file mode 100644 index 000000000..0519ecba6 --- /dev/null +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorInfo.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorInfo.java new file mode 100644 index 000000000..062b4eabf --- /dev/null +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorInfo.java @@ -0,0 +1,117 @@ +package com.databricks.sdk.core.error.details; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import java.util.Map; + +/** + * ErrorInfo describes the cause of the error with structured details. + * + *

This class provides structured information about what went wrong, including: + *

    + *
  • The reason for the error (a constant value identifying the proximate cause)
  • + *
  • The logical grouping to which the "reason" belongs
  • + *
  • Additional structured details about the error
  • + *
+ * + *

This information can be used by clients to: + *

    + *
  • Understand what went wrong
  • + *
  • Provide better error messages to users
  • + *
  • Implement appropriate error handling logic
  • + *
  • Debug issues more effectively
  • + *
+ */ +@AutoValue +@JsonDeserialize(builder = AutoValue_ErrorInfo.Builder.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class ErrorInfo { + + /** + * The reason of the error. This is a constant value that identifies the + * proximate cause of the error. + * + *

Examples might include: "INVALID_ARGUMENT", "RESOURCE_NOT_FOUND", + * "PERMISSION_DENIED", etc. + * + * @return the reason for the error + */ + @JsonProperty("reason") + public abstract String reason(); + + /** + * The logical grouping to which the "reason" belongs. + * + *

This provides context about the domain or category of the error. + * Examples might include: "databricks.api", "databricks.auth", etc. + * + * @return the domain of the error + */ + @JsonProperty("domain") + public abstract String domain(); + + /** + * Additional structured details about this error. + * + *

This map can contain arbitrary key-value pairs that provide + * additional context about the error. The exact contents depend on + * the specific error type and the service implementation. + * + * @return additional metadata about the error + */ + @JsonProperty("metadata") + public abstract Map metadata(); + + /** + * Creates a new builder for constructing ErrorInfo instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_ErrorInfo.Builder(); + } + + /** + * Builder for constructing ErrorInfo instances. + */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + + /** + * Sets the reason for the error. + * + * @param reason the reason for the error + * @return this builder for method chaining + */ + @JsonProperty("reason") + public abstract Builder reason(String reason); + + /** + * Sets the domain of the error. + * + * @param domain the domain of the error + * @return this builder for method chaining + */ + @JsonProperty("domain") + public abstract Builder domain(String domain); + + /** + * Sets the additional metadata about the error. + * + * @param metadata additional metadata about the error + * @return this builder for method chaining + */ + @JsonProperty("metadata") + public abstract Builder metadata(Map metadata); + + /** + * Builds the ErrorInfo instance. + * + * @return a new ErrorInfo instance + */ + public abstract ErrorInfo build(); + } +} \ No newline at end of file diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/Help.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/Help.java new file mode 100644 index 000000000..8b8cde452 --- /dev/null +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/Help.java @@ -0,0 +1,201 @@ +package com.databricks.sdk.core.error.details; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import java.util.List; + +/** + * Help provides links to documentation or for performing an out of band action. + * + *

For example, if a quota check failed with an error indicating the calling + * project hasn't enabled the accessed service, this can contain a URL pointing + * directly to the right place in the developer console to flip the bit. + * + *

Help information is particularly useful for: + *

    + *
  • Guiding users to self-service solutions
  • + *
  • Providing context-specific documentation
  • + *
  • Offering actionable steps to resolve issues
  • + *
  • Reducing support ticket volume
  • + *
  • Improving user experience during error scenarios
  • + *
+ * + *

This type of error detail helps transform error responses from simple + * failure notifications into actionable guidance that users can follow to + * resolve their issues. + */ +@AutoValue +@JsonDeserialize(builder = AutoValue_Help.Builder.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class Help { + + /** + * URL(s) pointing to additional information on handling the current error. + * + *

This list contains links to resources that can help users understand + * and resolve the error they encountered. Each link should provide specific, + * actionable guidance related to the current error context. + * + *

Examples of helpful links might include: + *

    + *
  • Documentation pages explaining the error
  • + *
  • Troubleshooting guides
  • + *
  • Configuration pages in developer consoles
  • + *
  • Support contact forms
  • + *
  • Community forums or knowledge bases
  • + *
  • Video tutorials or walkthroughs
  • + *
  • API reference documentation
  • + *
+ * + * @return the list of helpful links + */ + @JsonProperty("links") + public abstract List links(); + + /** + * Creates a new builder for constructing Help instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_Help.Builder(); + } + + /** + * Builder for constructing Help instances. + */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + + /** + * Sets the helpful links. + * + * @param links the list of helpful links + * @return this builder for method chaining + */ + @JsonProperty("links") + public abstract Builder links(List links); + + /** + * Builds the Help instance. + * + * @return a new Help instance + */ + public abstract Help build(); + } + + /** + * HelpLink provides a single helpful resource for resolving an error. + * + *

Each link should be specific to the error context and provide + * actionable guidance that users can follow to resolve their issue. + */ + @AutoValue + @JsonDeserialize(builder = AutoValue_Help_HelpLink.Builder.class) + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class HelpLink { + + /** + * Describes what the link offers. + * + *

This field provides a human-readable description of what users + * can expect to find when they follow the link. The description should + * be clear and specific enough for users to decide whether the link + * is relevant to their situation. + * + *

Examples of link descriptions: + *

    + *
  • "Enable the service in the developer console"
  • + *
  • "View API rate limiting documentation"
  • + *
  • "Check your authentication configuration"
  • + *
  • "Learn about required permissions"
  • + *
  • "Troubleshoot common connection issues"
  • + *
  • "Contact support for this error"
  • + *
  • "View example code and usage patterns"
  • + *
  • "Check service status and known issues"
  • + *
+ * + *

A good description helps users understand: + *

    + *
  • What the link will help them with
  • + *
  • Whether it's relevant to their specific error
  • + *
  • What they can expect to learn or accomplish
  • + *
+ * + * @return description of what the link offers + */ + @JsonProperty("description") + public abstract String description(); + + /** + * The URL of the link. + * + *

This field contains the actual URL that users can follow to access + * the helpful resource. The URL should be: + *

    + *
  • Accessible to the user (not requiring special access)
  • + *
  • Stable and not likely to change frequently
  • + *
  • Secure (HTTPS for web resources)
  • + *
  • Relevant to the current error context
  • + *
+ * + *

Examples of helpful URLs: + *

    + *
  • Documentation pages: "https://docs.example.com/errors/rate-limit"
  • + *
  • Developer console: "https://console.example.com/settings/api"
  • + *
  • Support forms: "https://support.example.com/contact"
  • + *
  • Knowledge base: "https://help.example.com/troubleshooting"
  • + *
  • Community forums: "https://community.example.com/errors"
  • + *
+ * + * @return the URL of the helpful resource + */ + @JsonProperty("url") + public abstract String url(); + + /** + * Creates a new builder for constructing HelpLink instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_Help_HelpLink.Builder(); + } + + /** + * Builder for constructing HelpLink instances. + */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + + /** + * Sets the link description. + * + * @param description description of what the link offers + * @return this builder for method chaining + */ + @JsonProperty("description") + public abstract Builder description(String description); + + /** + * Sets the link URL. + * + * @param url the URL of the helpful resource + * @return this builder for method chaining + */ + @JsonProperty("url") + public abstract Builder url(String url); + + /** + * Builds the HelpLink instance. + * + * @return a new HelpLink instance + */ + public abstract HelpLink build(); + } + } +} \ No newline at end of file diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/PreconditionFailure.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/PreconditionFailure.java new file mode 100644 index 000000000..c2dd429a8 --- /dev/null +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/PreconditionFailure.java @@ -0,0 +1,202 @@ +package com.databricks.sdk.core.error.details; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import java.util.List; + +/** + * PreconditionFailure describes what preconditions have failed. + * + *

This error type indicates that one or more conditions that must be met + * before a request can be processed have not been satisfied. Preconditions + * are typically business logic rules or system state requirements that must + * be true for the operation to succeed. + * + *

Examples of precondition failures might include: + *

    + *
  • Terms of service not accepted
  • + *
  • Required configuration not set
  • + *
  • System not in the correct state
  • + *
  • Required permissions not granted
  • + *
  • Dependencies not satisfied
  • + *
+ * + *

This information helps clients understand: + *

    + *
  • What conditions need to be met
  • + *
  • How to resolve the precondition issues
  • + *
  • What actions they need to take
  • + *
  • Whether the issue is resolvable by the client
  • + *
+ */ +@AutoValue +@JsonDeserialize(builder = AutoValue_PreconditionFailure.Builder.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class PreconditionFailure { + + /** + * Describes all precondition violations. + * + *

This list contains details about each specific precondition that + * failed. Multiple violations can occur if multiple preconditions are + * not met simultaneously. + * + * @return the list of precondition violations + */ + @JsonProperty("violations") + public abstract List violations(); + + /** + * Creates a new builder for constructing PreconditionFailure instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_PreconditionFailure.Builder(); + } + + /** + * Builder for constructing PreconditionFailure instances. + */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + + /** + * Sets the precondition violations. + * + * @param violations the list of precondition violations + * @return this builder for method chaining + */ + @JsonProperty("violations") + public abstract Builder violations(List violations); + + /** + * Builds the PreconditionFailure instance. + * + * @return a new PreconditionFailure instance + */ + public abstract PreconditionFailure build(); + } + + /** + * PreconditionFailureViolation describes a specific precondition violation. + * + *

Each violation provides details about what specific precondition + * failed and how the client can resolve the issue. + */ + @AutoValue + @JsonDeserialize(builder = AutoValue_PreconditionFailure_PreconditionFailureViolation.Builder.class) + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class PreconditionFailureViolation { + + /** + * The type of PreconditionFailure. + * + *

This field categorizes the type of precondition that failed. + * Examples might include: + *

    + *
  • "TERMS_OF_SERVICE"
  • + *
  • "CONFIGURATION"
  • + *
  • "SYSTEM_STATE"
  • + *
  • "PERMISSIONS"
  • + *
  • "DEPENDENCIES"
  • + *
+ * + * @return the type of precondition failure + */ + @JsonProperty("type") + public abstract String type(); + + /** + * The subject, relative to the type, that failed. + * + *

This field identifies the specific entity or resource that + * failed the precondition check. The interpretation depends on the + * type of precondition failure. + * + *

Examples: + *

    + *
  • For "TERMS_OF_SERVICE" type: user ID or account identifier
  • + *
  • For "CONFIGURATION" type: configuration key or setting name
  • + *
  • For "PERMISSIONS" type: resource name or permission scope
  • + *
  • For "DEPENDENCIES" type: service name or component identifier
  • + *
+ * + * @return the subject of the precondition failure + */ + @JsonProperty("subject") + public abstract String subject(); + + /** + * A description of how the precondition failed. Developers can use this + * description to understand how to fix the failure. + * + *

Examples of precondition failure descriptions: + *

    + *
  • "Terms of service not accepted"
  • + *
  • "Required configuration 'api_key' not set"
  • + *
  • "System is currently in maintenance mode"
  • + *
  • "Insufficient permissions to access resource"
  • + *
  • "Required service 'authentication' is not available"
  • + *
+ * + * @return description of the precondition failure + */ + @JsonProperty("description") + public abstract String description(); + + /** + * Creates a new builder for constructing PreconditionFailureViolation instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_PreconditionFailure_PreconditionFailureViolation.Builder(); + } + + /** + * Builder for constructing PreconditionFailureViolation instances. + */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + + /** + * Sets the type of precondition failure. + * + * @param type the type of precondition failure + * @return this builder for method chaining + */ + @JsonProperty("type") + public abstract Builder type(String type); + + /** + * Sets the subject of the precondition failure. + * + * @param subject the subject of the precondition failure + * @return this builder for method chaining + */ + @JsonProperty("subject") + public abstract Builder subject(String subject); + + /** + * Sets the description of the precondition failure. + * + * @param description description of the precondition failure + * @return this builder for method chaining + */ + @JsonProperty("description") + public abstract Builder description(String description); + + /** + * Builds the PreconditionFailureViolation instance. + * + * @return a new PreconditionFailureViolation instance + */ + public abstract PreconditionFailureViolation build(); + } + } +} \ No newline at end of file diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/QuotaFailure.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/QuotaFailure.java new file mode 100644 index 000000000..b43c3515c --- /dev/null +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/QuotaFailure.java @@ -0,0 +1,169 @@ +package com.databricks.sdk.core.error.details; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import java.util.List; + +/** + * QuotaFailure describes how a quota check failed. + * + *

For example, if a daily limit was exceeded for the calling project, + * a service could respond with a QuotaFailure detail containing the project + * id and the description of the quota limit that was exceeded. If the + * calling project hasn't enabled the service in the developer console, then + * a service could respond with the project id and set {@code service_disabled} + * to true. + * + *

Also see {@link RetryInfo} and {@link Help} types for other details + * about handling a quota failure. + * + *

This information helps clients understand: + *

    + *
  • What quota limits were exceeded
  • + *
  • How to resolve quota-related issues
  • + *
  • What actions they can take to continue
  • + *
  • Whether they need to contact support
  • + *
+ */ +@AutoValue +@JsonDeserialize(builder = AutoValue_QuotaFailure.Builder.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class QuotaFailure { + + /** + * Describes all quota violations. + * + *

This list contains details about each specific quota violation that + * occurred. Multiple violations can happen in a single request if multiple + * quota limits are exceeded simultaneously. + * + * @return the list of quota violations + */ + @JsonProperty("violations") + public abstract List violations(); + + /** + * Creates a new builder for constructing QuotaFailure instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_QuotaFailure.Builder(); + } + + /** + * Builder for constructing QuotaFailure instances. + */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + + /** + * Sets the quota violations. + * + * @param violations the list of quota violations + * @return this builder for method chaining + */ + @JsonProperty("violations") + public abstract Builder violations(List violations); + + /** + * Builds the QuotaFailure instance. + * + * @return a new QuotaFailure instance + */ + public abstract QuotaFailure build(); + } + + /** + * QuotaFailureViolation describes a specific quota violation. + * + *

Each violation provides details about what specific quota limit + * was exceeded and how the client can resolve the issue. + */ + @AutoValue + @JsonDeserialize(builder = AutoValue_QuotaFailure_QuotaFailureViolation.Builder.class) + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class QuotaFailureViolation { + + /** + * The subject on which the quota check failed. + * + *

This typically identifies the specific resource, project, user, + * or other entity that exceeded the quota limit. Examples might include: + *

    + *
  • Project ID
  • + *
  • User ID
  • + *
  • Resource name
  • + *
  • Service identifier
  • + *
+ * + * @return the subject of the quota violation + */ + @JsonProperty("subject") + public abstract String subject(); + + /** + * A description of how the quota check failed. Clients can use this + * description to find more about the quota configuration in the service's + * public documentation, or find the relevant quota limit to adjust through + * developer console. + * + *

Examples of quota violation descriptions: + *

    + *
  • "Service disabled"
  • + *
  • "Daily Limit for read operations exceeded"
  • + *
  • "Rate limit exceeded: 100 requests per minute"
  • + *
  • "Storage quota exceeded: 1GB limit"
  • + *
+ * + * @return description of the quota violation + */ + @JsonProperty("description") + public abstract String description(); + + /** + * Creates a new builder for constructing QuotaFailureViolation instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_QuotaFailure_QuotaFailureViolation.Builder(); + } + + /** + * Builder for constructing QuotaFailureViolation instances. + */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + + /** + * Sets the subject of the quota violation. + * + * @param subject the subject of the quota violation + * @return this builder for method chaining + */ + @JsonProperty("subject") + public abstract Builder subject(String subject); + + /** + * Sets the description of the quota violation. + * + * @param description description of the quota violation + * @return this builder for method chaining + */ + @JsonProperty("description") + public abstract Builder description(String description); + + /** + * Builds the QuotaFailureViolation instance. + * + * @return a new QuotaFailureViolation instance + */ + public abstract QuotaFailureViolation build(); + } + } +} \ No newline at end of file diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RequestInfo.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RequestInfo.java new file mode 100644 index 000000000..7172a6738 --- /dev/null +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RequestInfo.java @@ -0,0 +1,100 @@ +package com.databricks.sdk.core.error.details; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; + +/** + * RequestInfo contains metadata about the request that clients can attach when + * filing a bug or providing other forms of feedback. + * + *

This class provides information that can be used to: + *

    + *
  • Identify specific requests in service logs for debugging
  • + *
  • Provide context when reporting issues to support teams
  • + *
  • Correlate client-side errors with server-side logs
  • + *
  • Enable more effective troubleshooting of issues
  • + *
+ * + *

The information contained here is typically opaque to clients and should + * only be interpreted by the service that generated it. + */ +@AutoValue +@JsonDeserialize(builder = AutoValue_RequestInfo.Builder.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class RequestInfo { + + /** + * An opaque string that should only be interpreted by the service that + * generated it. For example, it can be used to identify requests in the + * service's logs. + * + *

This identifier is typically used by service operators and support + * teams to trace the execution of specific requests through the system. + * + * @return the request identifier + */ + @JsonProperty("request_id") + public abstract String requestId(); + + /** + * Any data that was used to serve this request. For example, an encrypted + * stack trace that can be sent back to the service provider for debugging. + * + *

This field may contain sensitive information that is useful for + * debugging but should be handled carefully. It might include: + *

    + *
  • Stack traces or error logs
  • + *
  • Request context or parameters
  • + *
  • System state information
  • + *
  • Other diagnostic data
  • + *
+ * + * @return data that was used to serve the request + */ + @JsonProperty("serving_data") + public abstract String servingData(); + + /** + * Creates a new builder for constructing RequestInfo instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_RequestInfo.Builder(); + } + + /** + * Builder for constructing RequestInfo instances. + */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + + /** + * Sets the request identifier. + * + * @param requestId the request identifier + * @return this builder for method chaining + */ + @JsonProperty("request_id") + public abstract Builder requestId(String requestId); + + /** + * Sets the serving data. + * + * @param servingData data that was used to serve the request + * @return this builder for method chaining + */ + @JsonProperty("serving_data") + public abstract Builder servingData(String servingData); + + /** + * Builds the RequestInfo instance. + * + * @return a new RequestInfo instance + */ + public abstract RequestInfo build(); + } +} \ No newline at end of file diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ResourceInfo.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ResourceInfo.java new file mode 100644 index 000000000..731eb6db4 --- /dev/null +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ResourceInfo.java @@ -0,0 +1,189 @@ +package com.databricks.sdk.core.error.details; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; + +/** + * ResourceInfo describes the resource that is being accessed. + * + *

This error detail type provides information about the specific resource + * that was involved in the error. This is particularly useful when the error + * is related to resource access, permissions, or resource-specific operations. + * + *

ResourceInfo helps clients understand: + *

    + *
  • What resource was involved in the error
  • + *
  • What type of resource it was
  • + *
  • Who owns the resource (if applicable)
  • + *
  • What specific error occurred when accessing the resource
  • + *
+ * + *

Examples of when ResourceInfo might be provided: + *

    + *
  • Permission denied errors
  • + *
  • Resource not found errors
  • + *
  • Resource conflict errors
  • + *
  • Resource quota exceeded errors
  • + *
  • Resource state validation errors
  • + *
+ */ +@AutoValue +@JsonDeserialize(builder = AutoValue_ResourceInfo.Builder.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class ResourceInfo { + + /** + * A name for the type of resource being accessed. + * + *

This field categorizes the resource by its type or class. Examples + * might include: + *

    + *
  • "workspace"
  • + *
  • "cluster"
  • + *
  • "job"
  • + *
  • "table"
  • + *
  • "user"
  • + *
  • "group"
  • + *
  • "secret"
  • + *
  • "notebook"
  • + *
+ * + *

This information helps clients understand what category of resource + * was involved and potentially implement resource-type-specific error handling. + * + * @return the type of resource + */ + @JsonProperty("resource_type") + public abstract String resourceType(); + + /** + * The name of the resource being accessed. + * + *

This field provides the specific identifier or name of the resource + * that was involved in the error. The format depends on the resource type + * but typically includes: + *

    + *
  • Resource IDs (e.g., "12345")
  • + *
  • Resource names (e.g., "my-cluster")
  • + *
  • Resource paths (e.g., "/path/to/resource")
  • + *
  • Resource URIs (e.g., "databricks://workspace/123")
  • + *
+ * + *

This identifier helps clients locate the specific resource and + * potentially retry the operation or report the issue more precisely. + * + * @return the name or identifier of the resource + */ + @JsonProperty("resource_name") + public abstract String resourceName(); + + /** + * The owner of the resource (optional). + * + *

This field identifies who owns or controls the resource. This + * information is particularly useful for permission-related errors or + * when the client needs to contact the resource owner. + * + *

Examples of owner values: + *

    + *
  • User email addresses
  • + *
  • User IDs
  • + *
  • Group names
  • + *
  • Service account identifiers
  • + *
  • Organization names
  • + *
+ * + *

Note: This field may be null or empty if the owner information + * is not available or not applicable. + * + * @return the owner of the resource, or null if not available + */ + @JsonProperty("owner") + public abstract String owner(); + + /** + * Describes what error is encountered when accessing this resource. + * + *

This field provides a human-readable description of the specific + * error that occurred when trying to access or operate on the resource. + * The description should be clear enough for developers to understand + * what went wrong. + * + *

Examples of resource error descriptions: + *

    + *
  • "Resource not found"
  • + *
  • "Access denied: insufficient permissions"
  • + *
  • "Resource is currently locked by another operation"
  • + *
  • "Resource quota exceeded"
  • + *
  • "Resource is in an invalid state for this operation"
  • + *
  • "Resource name contains invalid characters"
  • + *
  • "Resource already exists with this name"
  • + *
+ * + * @return description of the resource access error + */ + @JsonProperty("description") + public abstract String description(); + + /** + * Creates a new builder for constructing ResourceInfo instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_ResourceInfo.Builder(); + } + + /** + * Builder for constructing ResourceInfo instances. + */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + + /** + * Sets the resource type. + * + * @param resourceType the type of resource + * @return this builder for method chaining + */ + @JsonProperty("resource_type") + public abstract Builder resourceType(String resourceType); + + /** + * Sets the resource name. + * + * @param resourceName the name or identifier of the resource + * @return this builder for method chaining + */ + @JsonProperty("resource_name") + public abstract Builder resourceName(String resourceName); + + /** + * Sets the resource owner. + * + * @param owner the owner of the resource + * @return this builder for method chaining + */ + @JsonProperty("owner") + public abstract Builder owner(String owner); + + /** + * Sets the error description. + * + * @param description description of the resource access error + * @return this builder for method chaining + */ + @JsonProperty("description") + public abstract Builder description(String description); + + /** + * Builds the ResourceInfo instance. + * + * @return a new ResourceInfo instance + */ + public abstract ResourceInfo build(); + } +} \ No newline at end of file diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RetryInfo.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RetryInfo.java new file mode 100644 index 000000000..0a1d55fee --- /dev/null +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RetryInfo.java @@ -0,0 +1,130 @@ +package com.databricks.sdk.core.error.details; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.auto.value.AutoValue; +import java.io.IOException; +import java.time.Duration; + +/** + * RetryInfo describes when clients can retry a failed request. + * + *

Clients could ignore the recommendation here or retry when this information + * is missing from error responses. However, it's always recommended that clients + * should use exponential backoff when retrying. + * + *

Clients should wait until the {@code retryDelay} amount of time has passed + * since receiving the error response before retrying. If retrying requests also + * fail, clients should use an exponential backoff scheme to gradually increase + * the delay between retries based on {@code retryDelay}, until either a maximum + * number of retries have been reached or a maximum retry delay cap has been reached. + * + *

This information helps clients implement intelligent retry strategies that + * respect the service's recommendations while avoiding overwhelming the service + * with rapid retry attempts. + */ +@AutoValue +@JsonDeserialize(builder = AutoValue_RetryInfo.Builder.class) +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class RetryInfo { + + /** + * Clients should wait at least this long between retrying the same request. + * + *

Note: This is serialized as a string in the format "3.000000001s" where + * the string ends in the suffix "s" (indicating seconds) and is preceded by + * a decimal number of seconds. + * + *

Examples of valid formats: + *

    + *
  • "30s" - 30 seconds
  • + *
  • "1.5s" - 1.5 seconds
  • + *
  • "0.001s" - 1 millisecond
  • + *
  • "3.000000001s" - 3 seconds and 1 nanosecond
  • + *
+ * + * @return the recommended delay before retrying + */ + @JsonProperty("retry_delay") + @JsonDeserialize(using = RetryInfo.DurationDeserializer.class) + public abstract Duration retryDelay(); + + /** + * Creates a new builder for constructing RetryInfo instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_RetryInfo.Builder(); + } + + /** + * Builder for constructing RetryInfo instances. + */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + + /** + * Sets the retry delay. + * + * @param retryDelay the recommended delay before retrying + * @return this builder for method chaining + */ + @JsonProperty("retry_delay") + @JsonDeserialize(using = RetryInfo.DurationDeserializer.class) + public abstract Builder retryDelay(Duration retryDelay); + + /** + * Builds the RetryInfo instance. + * + * @return a new RetryInfo instance + */ + public abstract RetryInfo build(); + } + + /** + * Custom deserializer for Duration field to handle "30s" format. + * + *

This deserializer is strict and will reject invalid duration formats + * by throwing an IOException. Valid formats must: + *

    + *
  • Be a string value
  • + *
  • End with the 's' suffix
  • + *
  • Contain a valid decimal number before the suffix
  • + *
+ * + *

Examples of valid formats: "30s", "1.5s", "0.001s", "3.000000001s" + * Examples of invalid formats: "30", "30ms", "abc", 42 + */ + static class DurationDeserializer extends JsonDeserializer { + @Override + public Duration deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.getCodec().readTree(p); + + if (!node.isTextual()) { + throw new IOException("Duration must be a string, got: " + node.getNodeType()); + } + + // Parse duration string like "1.000000001s" or "30s" + String delayStr = node.asText(); + if (!delayStr.endsWith("s")) { + throw new IOException("Duration must end with 's' suffix: " + delayStr); + } + + try { + String secondsStr = delayStr.substring(0, delayStr.length() - 1); + double seconds = Double.parseDouble(secondsStr); + long nanos = (long) (seconds * 1_000_000_000); + return Duration.ofNanos(nanos); + } catch (NumberFormatException e) { + throw new IOException("Invalid duration format: " + delayStr, e); + } + } + } +} \ No newline at end of file diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/utils/SerDeUtils.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/utils/SerDeUtils.java index 9730b135e..be55ab0db 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/utils/SerDeUtils.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/utils/SerDeUtils.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.guava.GuavaModule; /** Utilities for serialization and deserialization in the Databricks Java SDK. */ public class SerDeUtils { @@ -12,6 +13,7 @@ public static ObjectMapper createMapper() { ObjectMapper mapper = new ObjectMapper(); mapper .registerModule(new JavaTimeModule()) + .registerModule(new GuavaModule()) .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) .configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true) 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 bad3dfadc..64b8d7f31 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 @@ -5,6 +5,8 @@ import com.databricks.sdk.core.error.ApiErrorBody; import com.databricks.sdk.core.error.ErrorDetail; import com.databricks.sdk.core.error.PrivateLinkValidationError; +import com.databricks.sdk.core.error.details.ErrorDetails; +import com.databricks.sdk.core.error.details.ErrorInfo; import com.databricks.sdk.core.http.Request; import com.databricks.sdk.core.http.Response; import com.databricks.sdk.core.utils.FakeTimer; @@ -317,10 +319,21 @@ void errorDetails() throws JsonProcessingException { Map metadata = new HashMap<>(); metadata.put("etag", "value"); - ErrorDetail errorDetails = - new ErrorDetail("type.googleapis.com/google.rpc.ErrorInfo", "reason", "domain", metadata); - ErrorDetail unrelatedDetails = - new ErrorDetail("unrelated", "wrong", "wrongDomain", new HashMap<>()); + + // Create ErrorDetails object instead of List + ErrorDetails errorDetails = ErrorDetails.builder() + .setErrorInfo(ErrorInfo.builder() + .reason("reason") + .domain("domain") + .metadata(metadata) + .build()) + .setUnknownDetails(Arrays.asList( + mapper.createObjectNode() + .put("@type", "unrelated") + .put("reason", "wrong") + .put("domain", "wrongDomain") + )) + .build(); DatabricksError error = runFailingApiClientTest( @@ -336,17 +349,17 @@ void errorDetails() throws JsonProcessingException { null, null, null, - Arrays.asList(errorDetails, unrelatedDetails))), + errorDetails)), getSuccessResponse(req)), MyEndpointResponse.class, DatabricksError.class); List responseErrors = error.getErrorInfo(); assertEquals(responseErrors.size(), 1); ErrorDetail responseError = responseErrors.get(0); - assertEquals(errorDetails.getType(), responseError.getType()); - assertEquals(errorDetails.getReason(), responseError.getReason()); - assertEquals(errorDetails.getMetadata(), responseError.getMetadata()); - assertEquals(errorDetails.getDomain(), responseError.getDomain()); + assertEquals("type.googleapis.com/google.rpc.ErrorInfo", responseError.getType()); + assertEquals("reason", responseError.getReason()); + assertEquals(metadata, responseError.getMetadata()); + assertEquals("domain", responseError.getDomain()); } @Test diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java new file mode 100644 index 000000000..9deed8234 --- /dev/null +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java @@ -0,0 +1,402 @@ +package com.databricks.sdk.core.error.details; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.datatype.guava.GuavaModule; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class ErrorDetailsTest { + + private final ObjectMapper mapper = new ObjectMapper() + .registerModule(new GuavaModule()); + + @Test + public void testDeserializeFromArray() throws Exception { + String json = "[" + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + + "\"reason\": \"PERMISSION_DENIED\"," + + "\"domain\": \"databricks.com\"," + + "\"metadata\": {" + + "\"service\": \"workspace\"" + + "}" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.RequestInfo\"," + + "\"request_id\": \"req-123\"," + + "\"serving_data\": \"stack-trace-data\"" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.RetryInfo\"," + + "\"retry_delay\": \"30s\"" + + "}," + + "{" + + "\"@type\": \"unknown.type\"," + + "\"custom_field\": \"custom_value\"" + + "}" + + "]"; + + ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); + + assertTrue(errorDetails.errorInfo().isPresent()); + assertEquals("PERMISSION_DENIED", errorDetails.errorInfo().get().reason()); + assertEquals("databricks.com", errorDetails.errorInfo().get().domain()); + + assertTrue(errorDetails.requestInfo().isPresent()); + assertEquals("req-123", errorDetails.requestInfo().get().requestId()); + assertEquals("stack-trace-data", errorDetails.requestInfo().get().servingData()); + + assertTrue(errorDetails.retryInfo().isPresent()); + assertEquals("PT30S", errorDetails.retryInfo().get().retryDelay().toString()); + + assertEquals(1, errorDetails.unknownDetails().size()); + JsonNode unknownDetail = errorDetails.unknownDetails().get(0); + assertEquals("unknown.type", unknownDetail.get("@type").asText()); + assertEquals("custom_value", unknownDetail.get("custom_field").asText()); + } + + @Test + public void testDeserializeFromSingleObject() throws Exception { + String json = "{" + + "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + + "\"reason\": \"RESOURCE_EXHAUSTED\"," + + "\"domain\": \"databricks.com\"," + + "\"metadata\": {" + + "\"quota_limit\": \"1000\"" + + "}" + + "}"; + + ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); + + assertTrue(errorDetails.errorInfo().isPresent()); + assertEquals("RESOURCE_EXHAUSTED", errorDetails.errorInfo().get().reason()); + assertEquals("databricks.com", errorDetails.errorInfo().get().domain()); + assertEquals("1000", errorDetails.errorInfo().get().metadata().get("quota_limit")); + + assertEquals(0, errorDetails.unknownDetails().size()); + } + + @Test + public void testDeserializeFromArrayWithMixedTypes() throws Exception { + String json = "[" + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.QuotaFailure\"," + + "\"violations\": [" + + "{" + + "\"subject\": \"project:123\"," + + "\"description\": \"Daily limit exceeded\"" + + "}" + + "]" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.Help\"," + + "\"links\": [" + + "{" + + "\"description\": \"Quota documentation\"," + + "\"url\": \"https://docs.databricks.com/quota\"" + + "}" + + "]" + + "}," + + "{" + + "\"unrecognized_field\": \"value\"" + + "}" + + "]"; + + ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); + + assertTrue(errorDetails.quotaFailure().isPresent()); + assertEquals(1, errorDetails.quotaFailure().get().violations().size()); + assertEquals("project:123", errorDetails.quotaFailure().get().violations().get(0).subject()); + assertEquals("Daily limit exceeded", errorDetails.quotaFailure().get().violations().get(0).description()); + + assertTrue(errorDetails.help().isPresent()); + assertEquals(1, errorDetails.help().get().links().size()); + assertEquals("Quota documentation", errorDetails.help().get().links().get(0).description()); + assertEquals("https://docs.databricks.com/quota", errorDetails.help().get().links().get(0).url()); + + assertEquals(1, errorDetails.unknownDetails().size()); + JsonNode unknownDetail = errorDetails.unknownDetails().get(0); + assertEquals("value", unknownDetail.get("unrecognized_field").asText()); + } + + @Test + public void testDeserializeEmptyArray() throws Exception { + String json = "[]"; + + ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); + + assertFalse(errorDetails.errorInfo().isPresent()); + assertFalse(errorDetails.requestInfo().isPresent()); + assertFalse(errorDetails.retryInfo().isPresent()); + assertFalse(errorDetails.debugInfo().isPresent()); + assertFalse(errorDetails.quotaFailure().isPresent()); + assertFalse(errorDetails.preconditionFailure().isPresent()); + assertFalse(errorDetails.badRequest().isPresent()); + assertFalse(errorDetails.resourceInfo().isPresent()); + assertFalse(errorDetails.help().isPresent()); + assertEquals(0, errorDetails.unknownDetails().size()); + } + + @Test + public void testDeserializeAllErrorDetailTypes() throws Exception { + String json = "[" + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + + "\"reason\": \"reason\"," + + "\"domain\": \"domain\"," + + "\"metadata\": {\"k1\": \"v1\", \"k2\": \"v2\"}" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.RequestInfo\"," + + "\"request_id\": \"req42\"," + + "\"serving_data\": \"data\"" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.RetryInfo\"," + + "\"retry_delay\": \"1.000000001s\"" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.DebugInfo\"," + + "\"stack_entries\": [\"entry1\", \"entry2\"]," + + "\"detail\": \"detail\"" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.QuotaFailure\"," + + "\"violations\": [{\"subject\": \"subject\", \"description\": \"description\"}]" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.PreconditionFailure\"," + + "\"violations\": [{\"type\": \"type\", \"subject\": \"subject\", \"description\": \"description\"}]" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.BadRequest\"," + + "\"field_violations\": [{\"field\": \"field\", \"description\": \"description\"}]" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.ResourceInfo\"," + + "\"resource_type\": \"resource_type\"," + + "\"resource_name\": \"resource_name\"," + + "\"owner\": \"owner\"," + + "\"description\": \"description\"" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.Help\"," + + "\"links\": [{\"description\": \"description\", \"url\": \"url\"}]" + + "}" + + "]"; + + ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); + + // Verify all error detail types are present + assertTrue(errorDetails.errorInfo().isPresent()); + assertEquals("reason", errorDetails.errorInfo().get().reason()); + assertEquals("domain", errorDetails.errorInfo().get().domain()); + assertEquals("v1", errorDetails.errorInfo().get().metadata().get("k1")); + assertEquals("v2", errorDetails.errorInfo().get().metadata().get("k2")); + + assertTrue(errorDetails.requestInfo().isPresent()); + assertEquals("req42", errorDetails.requestInfo().get().requestId()); + assertEquals("data", errorDetails.requestInfo().get().servingData()); + + assertTrue(errorDetails.retryInfo().isPresent()); + assertEquals("PT1.000000001S", errorDetails.retryInfo().get().retryDelay().toString()); + + assertTrue(errorDetails.debugInfo().isPresent()); + assertEquals(2, errorDetails.debugInfo().get().stackEntries().size()); + assertEquals("entry1", errorDetails.debugInfo().get().stackEntries().get(0)); + assertEquals("entry2", errorDetails.debugInfo().get().stackEntries().get(1)); + assertEquals("detail", errorDetails.debugInfo().get().detail()); + + assertTrue(errorDetails.quotaFailure().isPresent()); + assertEquals(1, errorDetails.quotaFailure().get().violations().size()); + assertEquals("subject", errorDetails.quotaFailure().get().violations().get(0).subject()); + assertEquals("description", errorDetails.quotaFailure().get().violations().get(0).description()); + + assertTrue(errorDetails.preconditionFailure().isPresent()); + assertEquals(1, errorDetails.preconditionFailure().get().violations().size()); + assertEquals("type", errorDetails.preconditionFailure().get().violations().get(0).type()); + assertEquals("subject", errorDetails.preconditionFailure().get().violations().get(0).subject()); + assertEquals("description", errorDetails.preconditionFailure().get().violations().get(0).description()); + + assertTrue(errorDetails.badRequest().isPresent()); + assertEquals(1, errorDetails.badRequest().get().fieldViolations().size()); + assertEquals("field", errorDetails.badRequest().get().fieldViolations().get(0).field()); + assertEquals("description", errorDetails.badRequest().get().fieldViolations().get(0).description()); + + assertTrue(errorDetails.resourceInfo().isPresent()); + assertEquals("resource_type", errorDetails.resourceInfo().get().resourceType()); + assertEquals("resource_name", errorDetails.resourceInfo().get().resourceName()); + assertEquals("owner", errorDetails.resourceInfo().get().owner()); + assertEquals("description", errorDetails.resourceInfo().get().description()); + + assertTrue(errorDetails.help().isPresent()); + assertEquals(1, errorDetails.help().get().links().size()); + assertEquals("description", errorDetails.help().get().links().get(0).description()); + assertEquals("url", errorDetails.help().get().links().get(0).url()); + + assertEquals(0, errorDetails.unknownDetails().size()); + } + + @Test + public void testDeserializeUnknownErrorDetailType() throws Exception { + String json = "[" + + "{" + + "\"@type\": \"foo\"," + + "\"reason\": \"reason\"" + + "}" + + "]"; + + ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); + + // Verify no known error detail types are present. + assertFalse(errorDetails.errorInfo().isPresent()); + assertFalse(errorDetails.requestInfo().isPresent()); + assertFalse(errorDetails.retryInfo().isPresent()); + assertFalse(errorDetails.debugInfo().isPresent()); + assertFalse(errorDetails.quotaFailure().isPresent()); + assertFalse(errorDetails.preconditionFailure().isPresent()); + assertFalse(errorDetails.badRequest().isPresent()); + assertFalse(errorDetails.resourceInfo().isPresent()); + assertFalse(errorDetails.help().isPresent()); + + // Verify unknown detail is captured. + assertEquals(1, errorDetails.unknownDetails().size()); + JsonNode unknownDetail = errorDetails.unknownDetails().get(0); + assertEquals("foo", unknownDetail.get("@type").asText()); + assertEquals("reason", unknownDetail.get("reason").asText()); + } + + @Test + public void testDeserializeInvalidErrorDetails() throws Exception { + String json = "[" + + "42," + + "\"foobar\"," + + "{\"foo\": \"bar\"}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + + "\"reason\": 0" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.RequestInfo\"," + + "\"request_id\": 0" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.RetryInfo\"," + + "\"retry_delay\": 0" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.DebugInfo\"," + + "\"stack_entries\": 0" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.QuotaFailure\"," + + "\"violations\": 0" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.PreconditionFailure\"," + + "\"violations\": 0" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.BadRequest\"," + + "\"field_violations\": 0" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.ResourceInfo\"," + + "\"resource_type\": 0" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.Help\"," + + "\"links\": 0" + + "}" + + "]"; + + ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); + + // All error detail types should fail to deserialize due to invalid data + // and be captured as unknown details. + assertFalse(errorDetails.errorInfo().isPresent()); + assertFalse(errorDetails.badRequest().isPresent()); + assertFalse(errorDetails.help().isPresent()); + assertFalse(errorDetails.requestInfo().isPresent()); + assertFalse(errorDetails.resourceInfo().isPresent()); + assertFalse(errorDetails.quotaFailure().isPresent()); + assertFalse(errorDetails.preconditionFailure().isPresent()); + assertFalse(errorDetails.debugInfo().isPresent()); + assertFalse(errorDetails.retryInfo().isPresent()); + + // 12 items should be captured as unknown details: + // - 3 non-error detail types (42, "foobar", {"foo": "bar"}), + // - 9 error detail objects with invalid data (including RetryInfo). + assertEquals(12, errorDetails.unknownDetails().size()); + + // Verify the first three unknown details (non-error detail types). + JsonNode firstUnknown = errorDetails.unknownDetails().get(0); + assertTrue(firstUnknown.isNumber()); + assertEquals(42, firstUnknown.asInt()); + + JsonNode secondUnknown = errorDetails.unknownDetails().get(1); + assertTrue(secondUnknown.isTextual()); + assertEquals("foobar", secondUnknown.asText()); + + JsonNode thirdUnknown = errorDetails.unknownDetails().get(2); + assertTrue(thirdUnknown.isObject()); + assertEquals("bar", thirdUnknown.get("foo").asText()); + + // Verify that error detail objects with invalid data are in unknown details + // Check for a few key types to ensure they're properly captured. + boolean foundErrorInfo = false; + boolean foundBadRequest = false; + for (JsonNode unknownDetail : errorDetails.unknownDetails()) { + if (unknownDetail.isObject() && unknownDetail.has("@type")) { + String type = unknownDetail.get("@type").asText(); + if ("type.googleapis.com/google.rpc.ErrorInfo".equals(type)) { + assertEquals(0, unknownDetail.get("reason").asInt()); + foundErrorInfo = true; + } else if ("type.googleapis.com/google.rpc.BadRequest".equals(type)) { + assertEquals(0, unknownDetail.get("field_violations").asInt()); + foundBadRequest = true; + } + } + } + + assertTrue(foundErrorInfo, "ErrorInfo should be found in unknown details"); + assertTrue(foundBadRequest, "BadRequest should be found in unknown details"); + } + + @Test + public void testDeserializeLastErrorDetailWins() throws Exception { + String json = "[" + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + + "\"reason\": \"first\"," + + "\"domain\": \"test\"," + + "\"metadata\": {}" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + + "\"reason\": \"second\"," + + "\"domain\": \"test\"," + + "\"metadata\": {}" + + "}" + + "]"; + + ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); + + // Verify only the last ErrorInfo is present. + assertTrue(errorDetails.errorInfo().isPresent()); + assertEquals("second", errorDetails.errorInfo().get().reason()); + + // Verify no other error detail types are present. + assertFalse(errorDetails.requestInfo().isPresent()); + assertFalse(errorDetails.retryInfo().isPresent()); + assertFalse(errorDetails.debugInfo().isPresent()); + assertFalse(errorDetails.quotaFailure().isPresent()); + assertFalse(errorDetails.preconditionFailure().isPresent()); + assertFalse(errorDetails.badRequest().isPresent()); + assertFalse(errorDetails.resourceInfo().isPresent()); + assertFalse(errorDetails.help().isPresent()); + + assertEquals(0, errorDetails.unknownDetails().size()); + } +} \ No newline at end of file From b1d08c9e440c13ea919b1753b1f9c02529d2e0a4 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 11 Aug 2025 20:20:16 +0000 Subject: [PATCH 02/14] fmt --- .../sdk/core/error/ApiErrorBody.java | 11 +- .../sdk/core/error/details/BadRequest.java | 294 ++++--- .../sdk/core/error/details/DebugInfo.java | 169 ++-- .../sdk/core/error/details/ErrorDetails.java | 93 +- .../details/ErrorDetailsDeserializer.java | 314 ++++--- .../core/error/details/ErrorDetailsTest.java | 1 - .../sdk/core/error/details/ErrorInfo.java | 162 ++-- .../sdk/core/error/details/Help.java | 330 ++++---- .../error/details/PreconditionFailure.java | 330 ++++---- .../sdk/core/error/details/QuotaFailure.java | 263 +++--- .../sdk/core/error/details/RequestInfo.java | 147 ++-- .../sdk/core/error/details/ResourceInfo.java | 302 +++---- .../sdk/core/error/details/RetryInfo.java | 208 +++-- .../databricks/sdk/core/utils/SerDeUtils.java | 2 +- .../databricks/sdk/core/ApiClientTest.java | 36 +- .../core/error/details/ErrorDetailsTest.java | 793 +++++++++--------- 16 files changed, 1722 insertions(+), 1733 deletions(-) delete mode 100644 databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java 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 ae2e1397c..fa3fe09e6 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,11 +1,11 @@ package com.databricks.sdk.core.error; +import com.databricks.sdk.core.error.details.ErrorDetails; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.databricks.sdk.core.error.details.ErrorDetails; -import java.util.List; import java.util.Arrays; import java.util.Collections; +import java.util.List; /** * The union of all JSON error responses from the Databricks APIs, not including HTML responses. @@ -104,6 +104,11 @@ private static List fromDetails(ErrorDetails details) { if (!details.errorInfo().isPresent()) { return Collections.emptyList(); } - return Arrays.asList(new ErrorDetail("type.googleapis.com/google.rpc.ErrorInfo", details.errorInfo().get().reason(), details.errorInfo().get().domain(), details.errorInfo().get().metadata())); + return Arrays.asList( + new ErrorDetail( + "type.googleapis.com/google.rpc.ErrorInfo", + details.errorInfo().get().reason(), + details.errorInfo().get().domain(), + details.errorInfo().get().metadata())); } } diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/BadRequest.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/BadRequest.java index ec1024605..e7b279d7c 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/BadRequest.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/BadRequest.java @@ -1,36 +1,37 @@ package com.databricks.sdk.core.error.details; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.auto.value.AutoValue; import java.util.List; /** - * BadRequest describes violations in a client request. This error type focuses - * on the syntactic aspects of the request. - * - *

BadRequest errors occur when the request format, structure, or content - * does not meet the service's requirements. This is different from business - * logic errors or system failures - it specifically indicates that the client - * sent a malformed or invalid request. - * + * BadRequest describes violations in a client request. This error type focuses on the syntactic + * aspects of the request. + * + *

BadRequest errors occur when the request format, structure, or content does not meet the + * service's requirements. This is different from business logic errors or system failures - it + * specifically indicates that the client sent a malformed or invalid request. + * *

Examples of bad request violations might include: + * *

    - *
  • Missing required fields
  • - *
  • Invalid field values (wrong type, format, or range)
  • - *
  • Malformed JSON or XML
  • - *
  • Unsupported field combinations
  • - *
  • Invalid enum values
  • - *
  • Field length or size violations
  • + *
  • Missing required fields + *
  • Invalid field values (wrong type, format, or range) + *
  • Malformed JSON or XML + *
  • Unsupported field combinations + *
  • Invalid enum values + *
  • Field length or size violations *
- * + * *

This information helps clients: + * *

    - *
  • Identify what's wrong with their request
  • - *
  • Fix the request format before retrying
  • - *
  • Understand the service's input requirements
  • - *
  • Implement proper input validation
  • + *
  • Identify what's wrong with their request + *
  • Fix the request format before retrying + *
  • Understand the service's input requirements + *
  • Implement proper input validation *
*/ @AutoValue @@ -38,149 +39,144 @@ @JsonIgnoreProperties(ignoreUnknown = true) public abstract class BadRequest { + /** + * Describes all field violations in the request. + * + *

This list contains details about each specific field or aspect of the request that violated + * the service's requirements. Multiple violations can occur if the request has multiple problems. + * + * @return the list of field violations + */ + @JsonProperty("field_violations") + public abstract List fieldViolations(); + + /** + * Creates a new builder for constructing BadRequest instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_BadRequest.Builder(); + } + + /** Builder for constructing BadRequest instances. */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + /** - * Describes all field violations in the request. - * - *

This list contains details about each specific field or aspect of - * the request that violated the service's requirements. Multiple violations - * can occur if the request has multiple problems. - * - * @return the list of field violations + * Sets the field violations. + * + * @param fieldViolations the list of field violations + * @return this builder for method chaining */ @JsonProperty("field_violations") - public abstract List fieldViolations(); + public abstract Builder fieldViolations(List fieldViolations); /** - * Creates a new builder for constructing BadRequest instances. - * - * @return a new builder instance + * Builds the BadRequest instance. + * + * @return a new BadRequest instance */ - public static Builder builder() { - return new AutoValue_BadRequest.Builder(); - } + public abstract BadRequest build(); + } + + /** + * BadRequestFieldViolation describes a specific field violation in a request. + * + *

Each violation provides details about what specific field or aspect of the request was + * invalid and how the client can fix it. + */ + @AutoValue + @JsonDeserialize(builder = AutoValue_BadRequest_BadRequestFieldViolation.Builder.class) + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class BadRequestFieldViolation { /** - * Builder for constructing BadRequest instances. + * A path leading to a field in the request body. + * + *

This field identifies the specific location of the violation within the request structure. + * The path format depends on the request format but typically follows a hierarchical structure. + * + *

Examples of field paths: + * + *

    + *
  • "name" - top-level field + *
  • "user.email" - nested field + *
  • "items[0].id" - array element field + *
  • "metadata.api_key" - deeply nested field + *
  • "settings.notifications.enabled" - multi-level nested field + *
+ * + *

This path helps clients quickly locate and fix the problematic field in their request. + * + * @return the path to the violating field */ - @AutoValue.Builder - @JsonIgnoreProperties(ignoreUnknown = true) - public abstract static class Builder { - - /** - * Sets the field violations. - * - * @param fieldViolations the list of field violations - * @return this builder for method chaining - */ - @JsonProperty("field_violations") - public abstract Builder fieldViolations(List fieldViolations); - - /** - * Builds the BadRequest instance. - * - * @return a new BadRequest instance - */ - public abstract BadRequest build(); - } + @JsonProperty("field") + public abstract String field(); /** - * BadRequestFieldViolation describes a specific field violation in a request. - * - *

Each violation provides details about what specific field or aspect - * of the request was invalid and how the client can fix it. + * A description of why the request element is bad. + * + *

This field provides a human-readable explanation of what's wrong with the field and how to + * fix it. The description should be clear enough for developers to understand and resolve the + * issue. + * + *

Examples of field violation descriptions: + * + *

    + *
  • "Field is required and cannot be empty" + *
  • "Value must be a positive integer" + *
  • "Invalid email format" + *
  • "String length must be between 1 and 100 characters" + *
  • "Unsupported enum value. Must be one of: [A, B, C]" + *
  • "Field cannot contain special characters" + *
  • "Date must be in ISO 8601 format (YYYY-MM-DD)" + *
+ * + * @return description of why the field is invalid */ - @AutoValue - @JsonDeserialize(builder = AutoValue_BadRequest_BadRequestFieldViolation.Builder.class) - @JsonIgnoreProperties(ignoreUnknown = true) - public abstract static class BadRequestFieldViolation { + @JsonProperty("description") + public abstract String description(); - /** - * A path leading to a field in the request body. - * - *

This field identifies the specific location of the violation - * within the request structure. The path format depends on the request - * format but typically follows a hierarchical structure. - * - *

Examples of field paths: - *

    - *
  • "name" - top-level field
  • - *
  • "user.email" - nested field
  • - *
  • "items[0].id" - array element field
  • - *
  • "metadata.api_key" - deeply nested field
  • - *
  • "settings.notifications.enabled" - multi-level nested field
  • - *
- * - *

This path helps clients quickly locate and fix the problematic - * field in their request. - * - * @return the path to the violating field - */ - @JsonProperty("field") - public abstract String field(); + /** + * Creates a new builder for constructing BadRequestFieldViolation instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_BadRequest_BadRequestFieldViolation.Builder(); + } + + /** Builder for constructing BadRequestFieldViolation instances. */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { - /** - * A description of why the request element is bad. - * - *

This field provides a human-readable explanation of what's wrong - * with the field and how to fix it. The description should be clear - * enough for developers to understand and resolve the issue. - * - *

Examples of field violation descriptions: - *

    - *
  • "Field is required and cannot be empty"
  • - *
  • "Value must be a positive integer"
  • - *
  • "Invalid email format"
  • - *
  • "String length must be between 1 and 100 characters"
  • - *
  • "Unsupported enum value. Must be one of: [A, B, C]"
  • - *
  • "Field cannot contain special characters"
  • - *
  • "Date must be in ISO 8601 format (YYYY-MM-DD)"
  • - *
- * - * @return description of why the field is invalid - */ - @JsonProperty("description") - public abstract String description(); + /** + * Sets the field path. + * + * @param field the path to the violating field + * @return this builder for method chaining + */ + @JsonProperty("field") + public abstract Builder field(String field); - /** - * Creates a new builder for constructing BadRequestFieldViolation instances. - * - * @return a new builder instance - */ - public static Builder builder() { - return new AutoValue_BadRequest_BadRequestFieldViolation.Builder(); - } + /** + * Sets the violation description. + * + * @param description description of why the field is invalid + * @return this builder for method chaining + */ + @JsonProperty("description") + public abstract Builder description(String description); - /** - * Builder for constructing BadRequestFieldViolation instances. - */ - @AutoValue.Builder - @JsonIgnoreProperties(ignoreUnknown = true) - public abstract static class Builder { - - /** - * Sets the field path. - * - * @param field the path to the violating field - * @return this builder for method chaining - */ - @JsonProperty("field") - public abstract Builder field(String field); - - /** - * Sets the violation description. - * - * @param description description of why the field is invalid - * @return this builder for method chaining - */ - @JsonProperty("description") - public abstract Builder description(String description); - - /** - * Builds the BadRequestFieldViolation instance. - * - * @return a new BadRequestFieldViolation instance - */ - public abstract BadRequestFieldViolation build(); - } + /** + * Builds the BadRequestFieldViolation instance. + * + * @return a new BadRequestFieldViolation instance + */ + public abstract BadRequestFieldViolation build(); } -} \ No newline at end of file + } +} diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/DebugInfo.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/DebugInfo.java index 11cc10d13..43f7beb48 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/DebugInfo.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/DebugInfo.java @@ -1,25 +1,25 @@ package com.databricks.sdk.core.error.details; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.auto.value.AutoValue; import java.util.List; /** * DebugInfo describes additional debugging information. - * - *

This class provides detailed information that can be used by developers - * and support teams to understand what went wrong and where the error occurred. - * The information is typically more technical and detailed than what would be - * shown to end users. - * + * + *

This class provides detailed information that can be used by developers and support teams to + * understand what went wrong and where the error occurred. The information is typically more + * technical and detailed than what would be shown to end users. + * *

DebugInfo is particularly useful for: + * *

    - *
  • Development and testing environments
  • - *
  • Support ticket investigations
  • - *
  • System debugging and troubleshooting
  • - *
  • Understanding the root cause of errors
  • + *
  • Development and testing environments + *
  • Support ticket investigations + *
  • System debugging and troubleshooting + *
  • Understanding the root cause of errors *
*/ @AutoValue @@ -27,87 +27,86 @@ @JsonIgnoreProperties(ignoreUnknown = true) public abstract class DebugInfo { + /** + * The stack trace entries indicating where the error occurred. + * + *

This list contains the call stack at the time the error occurred, typically starting from + * the most recent call and going backwards through the call chain. Each entry usually represents + * a method call or function invocation. + * + *

Stack trace information is invaluable for: + * + *

    + *
  • Identifying the exact location where an error occurred + *
  • Understanding the execution path that led to the error + *
  • Debugging complex error scenarios + *
  • Providing context to support teams + *
+ * + * @return the stack trace entries + */ + @JsonProperty("stack_entries") + public abstract List stackEntries(); + + /** + * Additional debugging information provided by the server. + * + *

This field can contain any additional context or details that the server deems useful for + * debugging purposes. The exact content depends on the service implementation and the specific + * error that occurred. + * + *

Examples of additional debugging information might include: + * + *

    + *
  • Internal error codes or identifiers + *
  • System state information + *
  • Configuration details + *
  • Performance metrics + *
  • Other diagnostic data + *
+ * + * @return additional debugging information + */ + @JsonProperty("detail") + public abstract String detail(); + + /** + * Creates a new builder for constructing DebugInfo instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_DebugInfo.Builder(); + } + + /** Builder for constructing DebugInfo instances. */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + /** - * The stack trace entries indicating where the error occurred. - * - *

This list contains the call stack at the time the error occurred, - * typically starting from the most recent call and going backwards through - * the call chain. Each entry usually represents a method call or function - * invocation. - * - *

Stack trace information is invaluable for: - *

    - *
  • Identifying the exact location where an error occurred
  • - *
  • Understanding the execution path that led to the error
  • - *
  • Debugging complex error scenarios
  • - *
  • Providing context to support teams
  • - *
- * - * @return the stack trace entries + * Sets the stack trace entries. + * + * @param stackEntries the stack trace entries + * @return this builder for method chaining */ @JsonProperty("stack_entries") - public abstract List stackEntries(); + public abstract Builder stackEntries(List stackEntries); /** - * Additional debugging information provided by the server. - * - *

This field can contain any additional context or details that the - * server deems useful for debugging purposes. The exact content depends - * on the service implementation and the specific error that occurred. - * - *

Examples of additional debugging information might include: - *

    - *
  • Internal error codes or identifiers
  • - *
  • System state information
  • - *
  • Configuration details
  • - *
  • Performance metrics
  • - *
  • Other diagnostic data
  • - *
- * - * @return additional debugging information + * Sets the additional debugging information. + * + * @param detail additional debugging information + * @return this builder for method chaining */ @JsonProperty("detail") - public abstract String detail(); - - /** - * Creates a new builder for constructing DebugInfo instances. - * - * @return a new builder instance - */ - public static Builder builder() { - return new AutoValue_DebugInfo.Builder(); - } + public abstract Builder detail(String detail); /** - * Builder for constructing DebugInfo instances. + * Builds the DebugInfo instance. + * + * @return a new DebugInfo instance */ - @AutoValue.Builder - @JsonIgnoreProperties(ignoreUnknown = true) - public abstract static class Builder { - - /** - * Sets the stack trace entries. - * - * @param stackEntries the stack trace entries - * @return this builder for method chaining - */ - @JsonProperty("stack_entries") - public abstract Builder stackEntries(List stackEntries); - - /** - * Sets the additional debugging information. - * - * @param detail additional debugging information - * @return this builder for method chaining - */ - @JsonProperty("detail") - public abstract Builder detail(String detail); - - /** - * Builds the DebugInfo instance. - * - * @return a new DebugInfo instance - */ - public abstract DebugInfo build(); - } -} \ No newline at end of file + public abstract DebugInfo build(); + } +} diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java index 808f181a0..3bd82c786 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java @@ -3,51 +3,60 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.auto.value.AutoValue; -import java.util.List; import java.util.Collections; +import java.util.List; import java.util.Optional; @AutoValue @JsonDeserialize(using = ErrorDetailsDeserializer.class) public abstract class ErrorDetails { - - public abstract Optional errorInfo(); - - public abstract Optional requestInfo(); - - public abstract Optional retryInfo(); - - public abstract Optional debugInfo(); - - public abstract Optional quotaFailure(); - - public abstract Optional preconditionFailure(); - - public abstract Optional badRequest(); - - public abstract Optional resourceInfo(); - - public abstract Optional help(); - - public abstract List unknownDetails(); - - public static Builder builder() { - return new AutoValue_ErrorDetails.Builder() - .setUnknownDetails(Collections.emptyList()); - } - - @AutoValue.Builder - public abstract static class Builder { - public abstract Builder setErrorInfo(ErrorInfo errorInfo); - public abstract Builder setRequestInfo(RequestInfo requestInfo); - public abstract Builder setRetryInfo(RetryInfo retryInfo); - public abstract Builder setDebugInfo(DebugInfo debugInfo); - public abstract Builder setQuotaFailure(QuotaFailure quotaFailure); - public abstract Builder setPreconditionFailure(PreconditionFailure preconditionFailure); - public abstract Builder setBadRequest(BadRequest badRequest); - public abstract Builder setResourceInfo(ResourceInfo resourceInfo); - public abstract Builder setHelp(Help help); - public abstract Builder setUnknownDetails(List unknownDetails); - public abstract ErrorDetails build(); - } -} \ No newline at end of file + + public abstract Optional errorInfo(); + + public abstract Optional requestInfo(); + + public abstract Optional retryInfo(); + + public abstract Optional debugInfo(); + + public abstract Optional quotaFailure(); + + public abstract Optional preconditionFailure(); + + public abstract Optional badRequest(); + + public abstract Optional resourceInfo(); + + public abstract Optional help(); + + public abstract List unknownDetails(); + + public static Builder builder() { + return new AutoValue_ErrorDetails.Builder().setUnknownDetails(Collections.emptyList()); + } + + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setErrorInfo(ErrorInfo errorInfo); + + public abstract Builder setRequestInfo(RequestInfo requestInfo); + + public abstract Builder setRetryInfo(RetryInfo retryInfo); + + public abstract Builder setDebugInfo(DebugInfo debugInfo); + + public abstract Builder setQuotaFailure(QuotaFailure quotaFailure); + + public abstract Builder setPreconditionFailure(PreconditionFailure preconditionFailure); + + public abstract Builder setBadRequest(BadRequest badRequest); + + public abstract Builder setResourceInfo(ResourceInfo resourceInfo); + + public abstract Builder setHelp(Help help); + + public abstract Builder setUnknownDetails(List unknownDetails); + + public abstract ErrorDetails build(); + } +} diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetailsDeserializer.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetailsDeserializer.java index ea5ec8989..867cf9ae3 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetailsDeserializer.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetailsDeserializer.java @@ -12,169 +12,163 @@ import java.util.List; public class ErrorDetailsDeserializer extends JsonDeserializer { - - private static final String ERROR_INFO_TYPE = "type.googleapis.com/google.rpc.ErrorInfo"; - private static final String REQUEST_INFO_TYPE = "type.googleapis.com/google.rpc.RequestInfo"; - private static final String RETRY_INFO_TYPE = "type.googleapis.com/google.rpc.RetryInfo"; - private static final String DEBUG_INFO_TYPE = "type.googleapis.com/google.rpc.DebugInfo"; - private static final String QUOTA_FAILURE_TYPE = "type.googleapis.com/google.rpc.QuotaFailure"; - private static final String PRECONDITION_FAILURE_TYPE = "type.googleapis.com/google.rpc.PreconditionFailure"; - private static final String BAD_REQUEST_TYPE = "type.googleapis.com/google.rpc.BadRequest"; - private static final String RESOURCE_INFO_TYPE = "type.googleapis.com/google.rpc.ResourceInfo"; - private static final String HELP_TYPE = "type.googleapis.com/google.rpc.Help"; - - @Override - public ErrorDetails deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - ObjectMapper mapper = (ObjectMapper) p.getCodec(); - JsonNode node = mapper.readTree(p); - - if (node.isArray()) { - return deserializeFromArray((ArrayNode) node, mapper); - } else { - // Fallback to single object deserialization - return deserializeFromObject((ObjectNode) node, mapper); - } - } - - private ErrorDetails deserializeFromArray(ArrayNode arrayNode, ObjectMapper mapper) throws IOException { - ErrorDetails.Builder builder = ErrorDetails.builder(); - List unknownDetails = new ArrayList<>(); - - for (JsonNode detailNode : arrayNode) { - if (detailNode.isObject()) { - ObjectNode objectNode = (ObjectNode) detailNode; - String type = objectNode.path("@type").asText(); - - try { - switch (type) { - case ERROR_INFO_TYPE: - ErrorInfo errorInfo = mapper.treeToValue(objectNode, ErrorInfo.class); - builder.setErrorInfo(errorInfo); - break; - case REQUEST_INFO_TYPE: - RequestInfo requestInfo = mapper.treeToValue(objectNode, RequestInfo.class); - builder.setRequestInfo(requestInfo); - break; - case RETRY_INFO_TYPE: - RetryInfo retryInfo = mapper.treeToValue(objectNode, RetryInfo.class); - builder.setRetryInfo(retryInfo); - break; - case DEBUG_INFO_TYPE: - DebugInfo debugInfo = mapper.treeToValue(objectNode, DebugInfo.class); - builder.setDebugInfo(debugInfo); - break; - case QUOTA_FAILURE_TYPE: - QuotaFailure quotaFailure = mapper.treeToValue(objectNode, QuotaFailure.class); - builder.setQuotaFailure(quotaFailure); - break; - case PRECONDITION_FAILURE_TYPE: - PreconditionFailure preconditionFailure = mapper.treeToValue(objectNode, PreconditionFailure.class); - builder.setPreconditionFailure(preconditionFailure); - break; - case BAD_REQUEST_TYPE: - BadRequest badRequest = mapper.treeToValue(objectNode, BadRequest.class); - builder.setBadRequest(badRequest); - break; - case RESOURCE_INFO_TYPE: - ResourceInfo resourceInfo = mapper.treeToValue(objectNode, ResourceInfo.class); - builder.setResourceInfo(resourceInfo); - break; - case HELP_TYPE: - Help help = mapper.treeToValue(objectNode, Help.class); - builder.setHelp(help); - break; - default: - // Unknown type, add to unknownDetails - unknownDetails.add(detailNode); - break; - } - } catch (Exception e) { - // If deserialization fails for a known type, treat it as unknown - unknownDetails.add(detailNode); - } - } else { - // Non-object node, add to unknownDetails - unknownDetails.add(detailNode); - } - } - - builder.setUnknownDetails(unknownDetails); - return builder.build(); + + private static final String ERROR_INFO_TYPE = "type.googleapis.com/google.rpc.ErrorInfo"; + private static final String REQUEST_INFO_TYPE = "type.googleapis.com/google.rpc.RequestInfo"; + private static final String RETRY_INFO_TYPE = "type.googleapis.com/google.rpc.RetryInfo"; + private static final String DEBUG_INFO_TYPE = "type.googleapis.com/google.rpc.DebugInfo"; + private static final String QUOTA_FAILURE_TYPE = "type.googleapis.com/google.rpc.QuotaFailure"; + private static final String PRECONDITION_FAILURE_TYPE = + "type.googleapis.com/google.rpc.PreconditionFailure"; + private static final String BAD_REQUEST_TYPE = "type.googleapis.com/google.rpc.BadRequest"; + private static final String RESOURCE_INFO_TYPE = "type.googleapis.com/google.rpc.ResourceInfo"; + private static final String HELP_TYPE = "type.googleapis.com/google.rpc.Help"; + + @Override + public ErrorDetails deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + ObjectMapper mapper = (ObjectMapper) p.getCodec(); + JsonNode node = mapper.readTree(p); + + if (node.isArray()) { + return deserializeFromArray((ArrayNode) node, mapper); + } else { + // Fallback to single object deserialization + return deserializeFromObject((ObjectNode) node, mapper); } - - private ErrorDetails deserializeFromObject(ObjectNode objectNode, ObjectMapper mapper) throws IOException { - // Handle single object case - try to determine type and deserialize accordingly + } + + private ErrorDetails deserializeFromArray(ArrayNode arrayNode, ObjectMapper mapper) + throws IOException { + ErrorDetails.Builder builder = ErrorDetails.builder(); + List unknownDetails = new ArrayList<>(); + + for (JsonNode detailNode : arrayNode) { + if (detailNode.isObject()) { + ObjectNode objectNode = (ObjectNode) detailNode; String type = objectNode.path("@type").asText(); - + try { - switch (type) { - case ERROR_INFO_TYPE: - ErrorInfo errorInfo = mapper.treeToValue(objectNode, ErrorInfo.class); - return ErrorDetails.builder() - .setErrorInfo(errorInfo) - .setUnknownDetails(new ArrayList<>()) - .build(); - case REQUEST_INFO_TYPE: - RequestInfo requestInfo = mapper.treeToValue(objectNode, RequestInfo.class); - return ErrorDetails.builder() - .setRequestInfo(requestInfo) - .setUnknownDetails(new ArrayList<>()) - .build(); - case RETRY_INFO_TYPE: - RetryInfo retryInfo = mapper.treeToValue(objectNode, RetryInfo.class); - return ErrorDetails.builder() - .setRetryInfo(retryInfo) - .setUnknownDetails(new ArrayList<>()) - .build(); - case DEBUG_INFO_TYPE: - DebugInfo debugInfo = mapper.treeToValue(objectNode, DebugInfo.class); - return ErrorDetails.builder() - .setDebugInfo(debugInfo) - .setUnknownDetails(new ArrayList<>()) - .build(); - case QUOTA_FAILURE_TYPE: - QuotaFailure quotaFailure = mapper.treeToValue(objectNode, QuotaFailure.class); - return ErrorDetails.builder() - .setQuotaFailure(quotaFailure) - .setUnknownDetails(new ArrayList<>()) - .build(); - case PRECONDITION_FAILURE_TYPE: - PreconditionFailure preconditionFailure = mapper.treeToValue(objectNode, PreconditionFailure.class); - return ErrorDetails.builder() - .setPreconditionFailure(preconditionFailure) - .build(); - case BAD_REQUEST_TYPE: - BadRequest badRequest = mapper.treeToValue(objectNode, BadRequest.class); - return ErrorDetails.builder() - .setBadRequest(badRequest) - .build(); - case RESOURCE_INFO_TYPE: - ResourceInfo resourceInfo = mapper.treeToValue(objectNode, ResourceInfo.class); - return ErrorDetails.builder() - .setResourceInfo(resourceInfo) - .setUnknownDetails(new ArrayList<>()) - .build(); - case HELP_TYPE: - Help help = mapper.treeToValue(objectNode, Help.class); - return ErrorDetails.builder() - .setHelp(help) - .setUnknownDetails(new ArrayList<>()) - .build(); - default: - // Unknown type, treat as unknown detail - List unknownDetails = new ArrayList<>(); - unknownDetails.add(objectNode); - return ErrorDetails.builder() - .setUnknownDetails(unknownDetails) - .build(); - } + switch (type) { + case ERROR_INFO_TYPE: + ErrorInfo errorInfo = mapper.treeToValue(objectNode, ErrorInfo.class); + builder.setErrorInfo(errorInfo); + break; + case REQUEST_INFO_TYPE: + RequestInfo requestInfo = mapper.treeToValue(objectNode, RequestInfo.class); + builder.setRequestInfo(requestInfo); + break; + case RETRY_INFO_TYPE: + RetryInfo retryInfo = mapper.treeToValue(objectNode, RetryInfo.class); + builder.setRetryInfo(retryInfo); + break; + case DEBUG_INFO_TYPE: + DebugInfo debugInfo = mapper.treeToValue(objectNode, DebugInfo.class); + builder.setDebugInfo(debugInfo); + break; + case QUOTA_FAILURE_TYPE: + QuotaFailure quotaFailure = mapper.treeToValue(objectNode, QuotaFailure.class); + builder.setQuotaFailure(quotaFailure); + break; + case PRECONDITION_FAILURE_TYPE: + PreconditionFailure preconditionFailure = + mapper.treeToValue(objectNode, PreconditionFailure.class); + builder.setPreconditionFailure(preconditionFailure); + break; + case BAD_REQUEST_TYPE: + BadRequest badRequest = mapper.treeToValue(objectNode, BadRequest.class); + builder.setBadRequest(badRequest); + break; + case RESOURCE_INFO_TYPE: + ResourceInfo resourceInfo = mapper.treeToValue(objectNode, ResourceInfo.class); + builder.setResourceInfo(resourceInfo); + break; + case HELP_TYPE: + Help help = mapper.treeToValue(objectNode, Help.class); + builder.setHelp(help); + break; + default: + // Unknown type, add to unknownDetails + unknownDetails.add(detailNode); + break; + } } catch (Exception e) { - // If deserialization fails, treat as unknown detail - List unknownDetails = new ArrayList<>(); - unknownDetails.add(objectNode); - return ErrorDetails.builder() - .setUnknownDetails(unknownDetails) - .build(); + // If deserialization fails for a known type, treat it as unknown + unknownDetails.add(detailNode); } + } else { + // Non-object node, add to unknownDetails + unknownDetails.add(detailNode); + } + } + + builder.setUnknownDetails(unknownDetails); + return builder.build(); + } + + private ErrorDetails deserializeFromObject(ObjectNode objectNode, ObjectMapper mapper) + throws IOException { + // Handle single object case - try to determine type and deserialize accordingly + String type = objectNode.path("@type").asText(); + + try { + switch (type) { + case ERROR_INFO_TYPE: + ErrorInfo errorInfo = mapper.treeToValue(objectNode, ErrorInfo.class); + return ErrorDetails.builder() + .setErrorInfo(errorInfo) + .setUnknownDetails(new ArrayList<>()) + .build(); + case REQUEST_INFO_TYPE: + RequestInfo requestInfo = mapper.treeToValue(objectNode, RequestInfo.class); + return ErrorDetails.builder() + .setRequestInfo(requestInfo) + .setUnknownDetails(new ArrayList<>()) + .build(); + case RETRY_INFO_TYPE: + RetryInfo retryInfo = mapper.treeToValue(objectNode, RetryInfo.class); + return ErrorDetails.builder() + .setRetryInfo(retryInfo) + .setUnknownDetails(new ArrayList<>()) + .build(); + case DEBUG_INFO_TYPE: + DebugInfo debugInfo = mapper.treeToValue(objectNode, DebugInfo.class); + return ErrorDetails.builder() + .setDebugInfo(debugInfo) + .setUnknownDetails(new ArrayList<>()) + .build(); + case QUOTA_FAILURE_TYPE: + QuotaFailure quotaFailure = mapper.treeToValue(objectNode, QuotaFailure.class); + return ErrorDetails.builder() + .setQuotaFailure(quotaFailure) + .setUnknownDetails(new ArrayList<>()) + .build(); + case PRECONDITION_FAILURE_TYPE: + PreconditionFailure preconditionFailure = + mapper.treeToValue(objectNode, PreconditionFailure.class); + return ErrorDetails.builder().setPreconditionFailure(preconditionFailure).build(); + case BAD_REQUEST_TYPE: + BadRequest badRequest = mapper.treeToValue(objectNode, BadRequest.class); + return ErrorDetails.builder().setBadRequest(badRequest).build(); + case RESOURCE_INFO_TYPE: + ResourceInfo resourceInfo = mapper.treeToValue(objectNode, ResourceInfo.class); + return ErrorDetails.builder() + .setResourceInfo(resourceInfo) + .setUnknownDetails(new ArrayList<>()) + .build(); + case HELP_TYPE: + Help help = mapper.treeToValue(objectNode, Help.class); + return ErrorDetails.builder().setHelp(help).setUnknownDetails(new ArrayList<>()).build(); + default: + // Unknown type, treat as unknown detail + List unknownDetails = new ArrayList<>(); + unknownDetails.add(objectNode); + return ErrorDetails.builder().setUnknownDetails(unknownDetails).build(); + } + } catch (Exception e) { + // If deserialization fails, treat as unknown detail + List unknownDetails = new ArrayList<>(); + unknownDetails.add(objectNode); + return ErrorDetails.builder().setUnknownDetails(unknownDetails).build(); } -} \ No newline at end of file + } +} diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java deleted file mode 100644 index 0519ecba6..000000000 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorInfo.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorInfo.java index 062b4eabf..06be58e82 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorInfo.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorInfo.java @@ -1,27 +1,29 @@ package com.databricks.sdk.core.error.details; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.auto.value.AutoValue; import java.util.Map; /** * ErrorInfo describes the cause of the error with structured details. - * + * *

This class provides structured information about what went wrong, including: + * *

    - *
  • The reason for the error (a constant value identifying the proximate cause)
  • - *
  • The logical grouping to which the "reason" belongs
  • - *
  • Additional structured details about the error
  • + *
  • The reason for the error (a constant value identifying the proximate cause) + *
  • The logical grouping to which the "reason" belongs + *
  • Additional structured details about the error *
- * + * *

This information can be used by clients to: + * *

    - *
  • Understand what went wrong
  • - *
  • Provide better error messages to users
  • - *
  • Implement appropriate error handling logic
  • - *
  • Debug issues more effectively
  • + *
  • Understand what went wrong + *
  • Provide better error messages to users + *
  • Implement appropriate error handling logic + *
  • Debug issues more effectively *
*/ @AutoValue @@ -29,89 +31,85 @@ @JsonIgnoreProperties(ignoreUnknown = true) public abstract class ErrorInfo { + /** + * The reason of the error. This is a constant value that identifies the proximate cause of the + * error. + * + *

Examples might include: "INVALID_ARGUMENT", "RESOURCE_NOT_FOUND", "PERMISSION_DENIED", etc. + * + * @return the reason for the error + */ + @JsonProperty("reason") + public abstract String reason(); + + /** + * The logical grouping to which the "reason" belongs. + * + *

This provides context about the domain or category of the error. Examples might include: + * "databricks.api", "databricks.auth", etc. + * + * @return the domain of the error + */ + @JsonProperty("domain") + public abstract String domain(); + + /** + * Additional structured details about this error. + * + *

This map can contain arbitrary key-value pairs that provide additional context about the + * error. The exact contents depend on the specific error type and the service implementation. + * + * @return additional metadata about the error + */ + @JsonProperty("metadata") + public abstract Map metadata(); + + /** + * Creates a new builder for constructing ErrorInfo instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_ErrorInfo.Builder(); + } + + /** Builder for constructing ErrorInfo instances. */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + /** - * The reason of the error. This is a constant value that identifies the - * proximate cause of the error. - * - *

Examples might include: "INVALID_ARGUMENT", "RESOURCE_NOT_FOUND", - * "PERMISSION_DENIED", etc. - * - * @return the reason for the error + * Sets the reason for the error. + * + * @param reason the reason for the error + * @return this builder for method chaining */ @JsonProperty("reason") - public abstract String reason(); + public abstract Builder reason(String reason); /** - * The logical grouping to which the "reason" belongs. - * - *

This provides context about the domain or category of the error. - * Examples might include: "databricks.api", "databricks.auth", etc. - * - * @return the domain of the error + * Sets the domain of the error. + * + * @param domain the domain of the error + * @return this builder for method chaining */ @JsonProperty("domain") - public abstract String domain(); + public abstract Builder domain(String domain); /** - * Additional structured details about this error. - * - *

This map can contain arbitrary key-value pairs that provide - * additional context about the error. The exact contents depend on - * the specific error type and the service implementation. - * - * @return additional metadata about the error + * Sets the additional metadata about the error. + * + * @param metadata additional metadata about the error + * @return this builder for method chaining */ @JsonProperty("metadata") - public abstract Map metadata(); - - /** - * Creates a new builder for constructing ErrorInfo instances. - * - * @return a new builder instance - */ - public static Builder builder() { - return new AutoValue_ErrorInfo.Builder(); - } + public abstract Builder metadata(Map metadata); /** - * Builder for constructing ErrorInfo instances. + * Builds the ErrorInfo instance. + * + * @return a new ErrorInfo instance */ - @AutoValue.Builder - @JsonIgnoreProperties(ignoreUnknown = true) - public abstract static class Builder { - - /** - * Sets the reason for the error. - * - * @param reason the reason for the error - * @return this builder for method chaining - */ - @JsonProperty("reason") - public abstract Builder reason(String reason); - - /** - * Sets the domain of the error. - * - * @param domain the domain of the error - * @return this builder for method chaining - */ - @JsonProperty("domain") - public abstract Builder domain(String domain); - - /** - * Sets the additional metadata about the error. - * - * @param metadata additional metadata about the error - * @return this builder for method chaining - */ - @JsonProperty("metadata") - public abstract Builder metadata(Map metadata); - - /** - * Builds the ErrorInfo instance. - * - * @return a new ErrorInfo instance - */ - public abstract ErrorInfo build(); - } -} \ No newline at end of file + public abstract ErrorInfo build(); + } +} diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/Help.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/Help.java index 8b8cde452..4b412ba9a 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/Help.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/Help.java @@ -1,201 +1,201 @@ package com.databricks.sdk.core.error.details; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.auto.value.AutoValue; import java.util.List; /** * Help provides links to documentation or for performing an out of band action. - * - *

For example, if a quota check failed with an error indicating the calling - * project hasn't enabled the accessed service, this can contain a URL pointing - * directly to the right place in the developer console to flip the bit. - * + * + *

For example, if a quota check failed with an error indicating the calling project hasn't + * enabled the accessed service, this can contain a URL pointing directly to the right place in the + * developer console to flip the bit. + * *

Help information is particularly useful for: + * *

    - *
  • Guiding users to self-service solutions
  • - *
  • Providing context-specific documentation
  • - *
  • Offering actionable steps to resolve issues
  • - *
  • Reducing support ticket volume
  • - *
  • Improving user experience during error scenarios
  • + *
  • Guiding users to self-service solutions + *
  • Providing context-specific documentation + *
  • Offering actionable steps to resolve issues + *
  • Reducing support ticket volume + *
  • Improving user experience during error scenarios *
- * - *

This type of error detail helps transform error responses from simple - * failure notifications into actionable guidance that users can follow to - * resolve their issues. + * + *

This type of error detail helps transform error responses from simple failure notifications + * into actionable guidance that users can follow to resolve their issues. */ @AutoValue @JsonDeserialize(builder = AutoValue_Help.Builder.class) @JsonIgnoreProperties(ignoreUnknown = true) public abstract class Help { + /** + * URL(s) pointing to additional information on handling the current error. + * + *

This list contains links to resources that can help users understand and resolve the error + * they encountered. Each link should provide specific, actionable guidance related to the current + * error context. + * + *

Examples of helpful links might include: + * + *

    + *
  • Documentation pages explaining the error + *
  • Troubleshooting guides + *
  • Configuration pages in developer consoles + *
  • Support contact forms + *
  • Community forums or knowledge bases + *
  • Video tutorials or walkthroughs + *
  • API reference documentation + *
+ * + * @return the list of helpful links + */ + @JsonProperty("links") + public abstract List links(); + + /** + * Creates a new builder for constructing Help instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_Help.Builder(); + } + + /** Builder for constructing Help instances. */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + + /** + * Sets the helpful links. + * + * @param links the list of helpful links + * @return this builder for method chaining + */ + @JsonProperty("links") + public abstract Builder links(List links); + + /** + * Builds the Help instance. + * + * @return a new Help instance + */ + public abstract Help build(); + } + + /** + * HelpLink provides a single helpful resource for resolving an error. + * + *

Each link should be specific to the error context and provide actionable guidance that users + * can follow to resolve their issue. + */ + @AutoValue + @JsonDeserialize(builder = AutoValue_Help_HelpLink.Builder.class) + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class HelpLink { + /** - * URL(s) pointing to additional information on handling the current error. - * - *

This list contains links to resources that can help users understand - * and resolve the error they encountered. Each link should provide specific, - * actionable guidance related to the current error context. - * - *

Examples of helpful links might include: + * Describes what the link offers. + * + *

This field provides a human-readable description of what users can expect to find when + * they follow the link. The description should be clear and specific enough for users to decide + * whether the link is relevant to their situation. + * + *

Examples of link descriptions: + * + *

    + *
  • "Enable the service in the developer console" + *
  • "View API rate limiting documentation" + *
  • "Check your authentication configuration" + *
  • "Learn about required permissions" + *
  • "Troubleshoot common connection issues" + *
  • "Contact support for this error" + *
  • "View example code and usage patterns" + *
  • "Check service status and known issues" + *
+ * + *

A good description helps users understand: + * *

    - *
  • Documentation pages explaining the error
  • - *
  • Troubleshooting guides
  • - *
  • Configuration pages in developer consoles
  • - *
  • Support contact forms
  • - *
  • Community forums or knowledge bases
  • - *
  • Video tutorials or walkthroughs
  • - *
  • API reference documentation
  • + *
  • What the link will help them with + *
  • Whether it's relevant to their specific error + *
  • What they can expect to learn or accomplish *
- * - * @return the list of helpful links + * + * @return description of what the link offers */ - @JsonProperty("links") - public abstract List links(); + @JsonProperty("description") + public abstract String description(); /** - * Creates a new builder for constructing Help instances. - * + * The URL of the link. + * + *

This field contains the actual URL that users can follow to access the helpful resource. + * The URL should be: + * + *

    + *
  • Accessible to the user (not requiring special access) + *
  • Stable and not likely to change frequently + *
  • Secure (HTTPS for web resources) + *
  • Relevant to the current error context + *
+ * + *

Examples of helpful URLs: + * + *

    + *
  • Documentation pages: "https://docs.example.com/errors/rate-limit" + *
  • Developer console: "https://console.example.com/settings/api" + *
  • Support forms: "https://support.example.com/contact" + *
  • Knowledge base: "https://help.example.com/troubleshooting" + *
  • Community forums: "https://community.example.com/errors" + *
+ * + * @return the URL of the helpful resource + */ + @JsonProperty("url") + public abstract String url(); + + /** + * Creates a new builder for constructing HelpLink instances. + * * @return a new builder instance */ public static Builder builder() { - return new AutoValue_Help.Builder(); + return new AutoValue_Help_HelpLink.Builder(); } - /** - * Builder for constructing Help instances. - */ + /** Builder for constructing HelpLink instances. */ @AutoValue.Builder @JsonIgnoreProperties(ignoreUnknown = true) public abstract static class Builder { - - /** - * Sets the helpful links. - * - * @param links the list of helpful links - * @return this builder for method chaining - */ - @JsonProperty("links") - public abstract Builder links(List links); - - /** - * Builds the Help instance. - * - * @return a new Help instance - */ - public abstract Help build(); - } - - /** - * HelpLink provides a single helpful resource for resolving an error. - * - *

Each link should be specific to the error context and provide - * actionable guidance that users can follow to resolve their issue. - */ - @AutoValue - @JsonDeserialize(builder = AutoValue_Help_HelpLink.Builder.class) - @JsonIgnoreProperties(ignoreUnknown = true) - public abstract static class HelpLink { - - /** - * Describes what the link offers. - * - *

This field provides a human-readable description of what users - * can expect to find when they follow the link. The description should - * be clear and specific enough for users to decide whether the link - * is relevant to their situation. - * - *

Examples of link descriptions: - *

    - *
  • "Enable the service in the developer console"
  • - *
  • "View API rate limiting documentation"
  • - *
  • "Check your authentication configuration"
  • - *
  • "Learn about required permissions"
  • - *
  • "Troubleshoot common connection issues"
  • - *
  • "Contact support for this error"
  • - *
  • "View example code and usage patterns"
  • - *
  • "Check service status and known issues"
  • - *
- * - *

A good description helps users understand: - *

    - *
  • What the link will help them with
  • - *
  • Whether it's relevant to their specific error
  • - *
  • What they can expect to learn or accomplish
  • - *
- * - * @return description of what the link offers - */ - @JsonProperty("description") - public abstract String description(); - /** - * The URL of the link. - * - *

This field contains the actual URL that users can follow to access - * the helpful resource. The URL should be: - *

    - *
  • Accessible to the user (not requiring special access)
  • - *
  • Stable and not likely to change frequently
  • - *
  • Secure (HTTPS for web resources)
  • - *
  • Relevant to the current error context
  • - *
- * - *

Examples of helpful URLs: - *

    - *
  • Documentation pages: "https://docs.example.com/errors/rate-limit"
  • - *
  • Developer console: "https://console.example.com/settings/api"
  • - *
  • Support forms: "https://support.example.com/contact"
  • - *
  • Knowledge base: "https://help.example.com/troubleshooting"
  • - *
  • Community forums: "https://community.example.com/errors"
  • - *
- * - * @return the URL of the helpful resource - */ - @JsonProperty("url") - public abstract String url(); + /** + * Sets the link description. + * + * @param description description of what the link offers + * @return this builder for method chaining + */ + @JsonProperty("description") + public abstract Builder description(String description); - /** - * Creates a new builder for constructing HelpLink instances. - * - * @return a new builder instance - */ - public static Builder builder() { - return new AutoValue_Help_HelpLink.Builder(); - } + /** + * Sets the link URL. + * + * @param url the URL of the helpful resource + * @return this builder for method chaining + */ + @JsonProperty("url") + public abstract Builder url(String url); - /** - * Builder for constructing HelpLink instances. - */ - @AutoValue.Builder - @JsonIgnoreProperties(ignoreUnknown = true) - public abstract static class Builder { - - /** - * Sets the link description. - * - * @param description description of what the link offers - * @return this builder for method chaining - */ - @JsonProperty("description") - public abstract Builder description(String description); - - /** - * Sets the link URL. - * - * @param url the URL of the helpful resource - * @return this builder for method chaining - */ - @JsonProperty("url") - public abstract Builder url(String url); - - /** - * Builds the HelpLink instance. - * - * @return a new HelpLink instance - */ - public abstract HelpLink build(); - } + /** + * Builds the HelpLink instance. + * + * @return a new HelpLink instance + */ + public abstract HelpLink build(); } -} \ No newline at end of file + } +} diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/PreconditionFailure.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/PreconditionFailure.java index c2dd429a8..fd026877e 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/PreconditionFailure.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/PreconditionFailure.java @@ -1,34 +1,35 @@ package com.databricks.sdk.core.error.details; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.auto.value.AutoValue; import java.util.List; /** * PreconditionFailure describes what preconditions have failed. - * - *

This error type indicates that one or more conditions that must be met - * before a request can be processed have not been satisfied. Preconditions - * are typically business logic rules or system state requirements that must - * be true for the operation to succeed. - * + * + *

This error type indicates that one or more conditions that must be met before a request can be + * processed have not been satisfied. Preconditions are typically business logic rules or system + * state requirements that must be true for the operation to succeed. + * *

Examples of precondition failures might include: + * *

    - *
  • Terms of service not accepted
  • - *
  • Required configuration not set
  • - *
  • System not in the correct state
  • - *
  • Required permissions not granted
  • - *
  • Dependencies not satisfied
  • + *
  • Terms of service not accepted + *
  • Required configuration not set + *
  • System not in the correct state + *
  • Required permissions not granted + *
  • Dependencies not satisfied *
- * + * *

This information helps clients understand: + * *

    - *
  • What conditions need to be met
  • - *
  • How to resolve the precondition issues
  • - *
  • What actions they need to take
  • - *
  • Whether the issue is resolvable by the client
  • + *
  • What conditions need to be met + *
  • How to resolve the precondition issues + *
  • What actions they need to take + *
  • Whether the issue is resolvable by the client *
*/ @AutoValue @@ -36,167 +37,164 @@ @JsonIgnoreProperties(ignoreUnknown = true) public abstract class PreconditionFailure { + /** + * Describes all precondition violations. + * + *

This list contains details about each specific precondition that failed. Multiple violations + * can occur if multiple preconditions are not met simultaneously. + * + * @return the list of precondition violations + */ + @JsonProperty("violations") + public abstract List violations(); + + /** + * Creates a new builder for constructing PreconditionFailure instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_PreconditionFailure.Builder(); + } + + /** Builder for constructing PreconditionFailure instances. */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + /** - * Describes all precondition violations. - * - *

This list contains details about each specific precondition that - * failed. Multiple violations can occur if multiple preconditions are - * not met simultaneously. - * - * @return the list of precondition violations + * Sets the precondition violations. + * + * @param violations the list of precondition violations + * @return this builder for method chaining */ @JsonProperty("violations") - public abstract List violations(); + public abstract Builder violations(List violations); + + /** + * Builds the PreconditionFailure instance. + * + * @return a new PreconditionFailure instance + */ + public abstract PreconditionFailure build(); + } + + /** + * PreconditionFailureViolation describes a specific precondition violation. + * + *

Each violation provides details about what specific precondition failed and how the client + * can resolve the issue. + */ + @AutoValue + @JsonDeserialize( + builder = AutoValue_PreconditionFailure_PreconditionFailureViolation.Builder.class) + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class PreconditionFailureViolation { /** - * Creates a new builder for constructing PreconditionFailure instances. - * + * The type of PreconditionFailure. + * + *

This field categorizes the type of precondition that failed. Examples might include: + * + *

    + *
  • "TERMS_OF_SERVICE" + *
  • "CONFIGURATION" + *
  • "SYSTEM_STATE" + *
  • "PERMISSIONS" + *
  • "DEPENDENCIES" + *
+ * + * @return the type of precondition failure + */ + @JsonProperty("type") + public abstract String type(); + + /** + * The subject, relative to the type, that failed. + * + *

This field identifies the specific entity or resource that failed the precondition check. + * The interpretation depends on the type of precondition failure. + * + *

Examples: + * + *

    + *
  • For "TERMS_OF_SERVICE" type: user ID or account identifier + *
  • For "CONFIGURATION" type: configuration key or setting name + *
  • For "PERMISSIONS" type: resource name or permission scope + *
  • For "DEPENDENCIES" type: service name or component identifier + *
+ * + * @return the subject of the precondition failure + */ + @JsonProperty("subject") + public abstract String subject(); + + /** + * A description of how the precondition failed. Developers can use this description to + * understand how to fix the failure. + * + *

Examples of precondition failure descriptions: + * + *

    + *
  • "Terms of service not accepted" + *
  • "Required configuration 'api_key' not set" + *
  • "System is currently in maintenance mode" + *
  • "Insufficient permissions to access resource" + *
  • "Required service 'authentication' is not available" + *
+ * + * @return description of the precondition failure + */ + @JsonProperty("description") + public abstract String description(); + + /** + * Creates a new builder for constructing PreconditionFailureViolation instances. + * * @return a new builder instance */ public static Builder builder() { - return new AutoValue_PreconditionFailure.Builder(); + return new AutoValue_PreconditionFailure_PreconditionFailureViolation.Builder(); } - /** - * Builder for constructing PreconditionFailure instances. - */ + /** Builder for constructing PreconditionFailureViolation instances. */ @AutoValue.Builder @JsonIgnoreProperties(ignoreUnknown = true) public abstract static class Builder { - - /** - * Sets the precondition violations. - * - * @param violations the list of precondition violations - * @return this builder for method chaining - */ - @JsonProperty("violations") - public abstract Builder violations(List violations); - - /** - * Builds the PreconditionFailure instance. - * - * @return a new PreconditionFailure instance - */ - public abstract PreconditionFailure build(); - } - /** - * PreconditionFailureViolation describes a specific precondition violation. - * - *

Each violation provides details about what specific precondition - * failed and how the client can resolve the issue. - */ - @AutoValue - @JsonDeserialize(builder = AutoValue_PreconditionFailure_PreconditionFailureViolation.Builder.class) - @JsonIgnoreProperties(ignoreUnknown = true) - public abstract static class PreconditionFailureViolation { - - /** - * The type of PreconditionFailure. - * - *

This field categorizes the type of precondition that failed. - * Examples might include: - *

    - *
  • "TERMS_OF_SERVICE"
  • - *
  • "CONFIGURATION"
  • - *
  • "SYSTEM_STATE"
  • - *
  • "PERMISSIONS"
  • - *
  • "DEPENDENCIES"
  • - *
- * - * @return the type of precondition failure - */ - @JsonProperty("type") - public abstract String type(); - - /** - * The subject, relative to the type, that failed. - * - *

This field identifies the specific entity or resource that - * failed the precondition check. The interpretation depends on the - * type of precondition failure. - * - *

Examples: - *

    - *
  • For "TERMS_OF_SERVICE" type: user ID or account identifier
  • - *
  • For "CONFIGURATION" type: configuration key or setting name
  • - *
  • For "PERMISSIONS" type: resource name or permission scope
  • - *
  • For "DEPENDENCIES" type: service name or component identifier
  • - *
- * - * @return the subject of the precondition failure - */ - @JsonProperty("subject") - public abstract String subject(); - - /** - * A description of how the precondition failed. Developers can use this - * description to understand how to fix the failure. - * - *

Examples of precondition failure descriptions: - *

    - *
  • "Terms of service not accepted"
  • - *
  • "Required configuration 'api_key' not set"
  • - *
  • "System is currently in maintenance mode"
  • - *
  • "Insufficient permissions to access resource"
  • - *
  • "Required service 'authentication' is not available"
  • - *
- * - * @return description of the precondition failure - */ - @JsonProperty("description") - public abstract String description(); - - /** - * Creates a new builder for constructing PreconditionFailureViolation instances. - * - * @return a new builder instance - */ - public static Builder builder() { - return new AutoValue_PreconditionFailure_PreconditionFailureViolation.Builder(); - } - - /** - * Builder for constructing PreconditionFailureViolation instances. - */ - @AutoValue.Builder - @JsonIgnoreProperties(ignoreUnknown = true) - public abstract static class Builder { - - /** - * Sets the type of precondition failure. - * - * @param type the type of precondition failure - * @return this builder for method chaining - */ - @JsonProperty("type") - public abstract Builder type(String type); - - /** - * Sets the subject of the precondition failure. - * - * @param subject the subject of the precondition failure - * @return this builder for method chaining - */ - @JsonProperty("subject") - public abstract Builder subject(String subject); - - /** - * Sets the description of the precondition failure. - * - * @param description description of the precondition failure - * @return this builder for method chaining - */ - @JsonProperty("description") - public abstract Builder description(String description); - - /** - * Builds the PreconditionFailureViolation instance. - * - * @return a new PreconditionFailureViolation instance - */ - public abstract PreconditionFailureViolation build(); - } + /** + * Sets the type of precondition failure. + * + * @param type the type of precondition failure + * @return this builder for method chaining + */ + @JsonProperty("type") + public abstract Builder type(String type); + + /** + * Sets the subject of the precondition failure. + * + * @param subject the subject of the precondition failure + * @return this builder for method chaining + */ + @JsonProperty("subject") + public abstract Builder subject(String subject); + + /** + * Sets the description of the precondition failure. + * + * @param description description of the precondition failure + * @return this builder for method chaining + */ + @JsonProperty("description") + public abstract Builder description(String description); + + /** + * Builds the PreconditionFailureViolation instance. + * + * @return a new PreconditionFailureViolation instance + */ + public abstract PreconditionFailureViolation build(); } -} \ No newline at end of file + } +} diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/QuotaFailure.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/QuotaFailure.java index b43c3515c..8ff6c80c5 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/QuotaFailure.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/QuotaFailure.java @@ -1,30 +1,29 @@ package com.databricks.sdk.core.error.details; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.auto.value.AutoValue; import java.util.List; /** * QuotaFailure describes how a quota check failed. - * - *

For example, if a daily limit was exceeded for the calling project, - * a service could respond with a QuotaFailure detail containing the project - * id and the description of the quota limit that was exceeded. If the - * calling project hasn't enabled the service in the developer console, then - * a service could respond with the project id and set {@code service_disabled} - * to true. - * - *

Also see {@link RetryInfo} and {@link Help} types for other details - * about handling a quota failure. - * + * + *

For example, if a daily limit was exceeded for the calling project, a service could respond + * with a QuotaFailure detail containing the project id and the description of the quota limit that + * was exceeded. If the calling project hasn't enabled the service in the developer console, then a + * service could respond with the project id and set {@code service_disabled} to true. + * + *

Also see {@link RetryInfo} and {@link Help} types for other details about handling a quota + * failure. + * *

This information helps clients understand: + * *

    - *
  • What quota limits were exceeded
  • - *
  • How to resolve quota-related issues
  • - *
  • What actions they can take to continue
  • - *
  • Whether they need to contact support
  • + *
  • What quota limits were exceeded + *
  • How to resolve quota-related issues + *
  • What actions they can take to continue + *
  • Whether they need to contact support *
*/ @AutoValue @@ -32,138 +31,134 @@ @JsonIgnoreProperties(ignoreUnknown = true) public abstract class QuotaFailure { + /** + * Describes all quota violations. + * + *

This list contains details about each specific quota violation that occurred. Multiple + * violations can happen in a single request if multiple quota limits are exceeded simultaneously. + * + * @return the list of quota violations + */ + @JsonProperty("violations") + public abstract List violations(); + + /** + * Creates a new builder for constructing QuotaFailure instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_QuotaFailure.Builder(); + } + + /** Builder for constructing QuotaFailure instances. */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + /** - * Describes all quota violations. - * - *

This list contains details about each specific quota violation that - * occurred. Multiple violations can happen in a single request if multiple - * quota limits are exceeded simultaneously. - * - * @return the list of quota violations + * Sets the quota violations. + * + * @param violations the list of quota violations + * @return this builder for method chaining */ @JsonProperty("violations") - public abstract List violations(); + public abstract Builder violations(List violations); /** - * Creates a new builder for constructing QuotaFailure instances. - * - * @return a new builder instance + * Builds the QuotaFailure instance. + * + * @return a new QuotaFailure instance */ - public static Builder builder() { - return new AutoValue_QuotaFailure.Builder(); - } + public abstract QuotaFailure build(); + } + + /** + * QuotaFailureViolation describes a specific quota violation. + * + *

Each violation provides details about what specific quota limit was exceeded and how the + * client can resolve the issue. + */ + @AutoValue + @JsonDeserialize(builder = AutoValue_QuotaFailure_QuotaFailureViolation.Builder.class) + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class QuotaFailureViolation { /** - * Builder for constructing QuotaFailure instances. + * The subject on which the quota check failed. + * + *

This typically identifies the specific resource, project, user, or other entity that + * exceeded the quota limit. Examples might include: + * + *

    + *
  • Project ID + *
  • User ID + *
  • Resource name + *
  • Service identifier + *
+ * + * @return the subject of the quota violation */ - @AutoValue.Builder - @JsonIgnoreProperties(ignoreUnknown = true) - public abstract static class Builder { - - /** - * Sets the quota violations. - * - * @param violations the list of quota violations - * @return this builder for method chaining - */ - @JsonProperty("violations") - public abstract Builder violations(List violations); - - /** - * Builds the QuotaFailure instance. - * - * @return a new QuotaFailure instance - */ - public abstract QuotaFailure build(); - } + @JsonProperty("subject") + public abstract String subject(); /** - * QuotaFailureViolation describes a specific quota violation. - * - *

Each violation provides details about what specific quota limit - * was exceeded and how the client can resolve the issue. + * A description of how the quota check failed. Clients can use this description to find more + * about the quota configuration in the service's public documentation, or find the relevant + * quota limit to adjust through developer console. + * + *

Examples of quota violation descriptions: + * + *

    + *
  • "Service disabled" + *
  • "Daily Limit for read operations exceeded" + *
  • "Rate limit exceeded: 100 requests per minute" + *
  • "Storage quota exceeded: 1GB limit" + *
+ * + * @return description of the quota violation */ - @AutoValue - @JsonDeserialize(builder = AutoValue_QuotaFailure_QuotaFailureViolation.Builder.class) - @JsonIgnoreProperties(ignoreUnknown = true) - public abstract static class QuotaFailureViolation { + @JsonProperty("description") + public abstract String description(); - /** - * The subject on which the quota check failed. - * - *

This typically identifies the specific resource, project, user, - * or other entity that exceeded the quota limit. Examples might include: - *

    - *
  • Project ID
  • - *
  • User ID
  • - *
  • Resource name
  • - *
  • Service identifier
  • - *
- * - * @return the subject of the quota violation - */ - @JsonProperty("subject") - public abstract String subject(); + /** + * Creates a new builder for constructing QuotaFailureViolation instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_QuotaFailure_QuotaFailureViolation.Builder(); + } + + /** Builder for constructing QuotaFailureViolation instances. */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { - /** - * A description of how the quota check failed. Clients can use this - * description to find more about the quota configuration in the service's - * public documentation, or find the relevant quota limit to adjust through - * developer console. - * - *

Examples of quota violation descriptions: - *

    - *
  • "Service disabled"
  • - *
  • "Daily Limit for read operations exceeded"
  • - *
  • "Rate limit exceeded: 100 requests per minute"
  • - *
  • "Storage quota exceeded: 1GB limit"
  • - *
- * - * @return description of the quota violation - */ - @JsonProperty("description") - public abstract String description(); + /** + * Sets the subject of the quota violation. + * + * @param subject the subject of the quota violation + * @return this builder for method chaining + */ + @JsonProperty("subject") + public abstract Builder subject(String subject); - /** - * Creates a new builder for constructing QuotaFailureViolation instances. - * - * @return a new builder instance - */ - public static Builder builder() { - return new AutoValue_QuotaFailure_QuotaFailureViolation.Builder(); - } + /** + * Sets the description of the quota violation. + * + * @param description description of the quota violation + * @return this builder for method chaining + */ + @JsonProperty("description") + public abstract Builder description(String description); - /** - * Builder for constructing QuotaFailureViolation instances. - */ - @AutoValue.Builder - @JsonIgnoreProperties(ignoreUnknown = true) - public abstract static class Builder { - - /** - * Sets the subject of the quota violation. - * - * @param subject the subject of the quota violation - * @return this builder for method chaining - */ - @JsonProperty("subject") - public abstract Builder subject(String subject); - - /** - * Sets the description of the quota violation. - * - * @param description description of the quota violation - * @return this builder for method chaining - */ - @JsonProperty("description") - public abstract Builder description(String description); - - /** - * Builds the QuotaFailureViolation instance. - * - * @return a new QuotaFailureViolation instance - */ - public abstract QuotaFailureViolation build(); - } + /** + * Builds the QuotaFailureViolation instance. + * + * @return a new QuotaFailureViolation instance + */ + public abstract QuotaFailureViolation build(); } -} \ No newline at end of file + } +} diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RequestInfo.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RequestInfo.java index 7172a6738..63a1c59cf 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RequestInfo.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RequestInfo.java @@ -1,100 +1,99 @@ package com.databricks.sdk.core.error.details; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.auto.value.AutoValue; /** - * RequestInfo contains metadata about the request that clients can attach when - * filing a bug or providing other forms of feedback. - * + * RequestInfo contains metadata about the request that clients can attach when filing a bug or + * providing other forms of feedback. + * *

This class provides information that can be used to: + * *

    - *
  • Identify specific requests in service logs for debugging
  • - *
  • Provide context when reporting issues to support teams
  • - *
  • Correlate client-side errors with server-side logs
  • - *
  • Enable more effective troubleshooting of issues
  • + *
  • Identify specific requests in service logs for debugging + *
  • Provide context when reporting issues to support teams + *
  • Correlate client-side errors with server-side logs + *
  • Enable more effective troubleshooting of issues *
- * - *

The information contained here is typically opaque to clients and should - * only be interpreted by the service that generated it. + * + *

The information contained here is typically opaque to clients and should only be interpreted + * by the service that generated it. */ @AutoValue @JsonDeserialize(builder = AutoValue_RequestInfo.Builder.class) @JsonIgnoreProperties(ignoreUnknown = true) public abstract class RequestInfo { + /** + * An opaque string that should only be interpreted by the service that generated it. For example, + * it can be used to identify requests in the service's logs. + * + *

This identifier is typically used by service operators and support teams to trace the + * execution of specific requests through the system. + * + * @return the request identifier + */ + @JsonProperty("request_id") + public abstract String requestId(); + + /** + * Any data that was used to serve this request. For example, an encrypted stack trace that can be + * sent back to the service provider for debugging. + * + *

This field may contain sensitive information that is useful for debugging but should be + * handled carefully. It might include: + * + *

    + *
  • Stack traces or error logs + *
  • Request context or parameters + *
  • System state information + *
  • Other diagnostic data + *
+ * + * @return data that was used to serve the request + */ + @JsonProperty("serving_data") + public abstract String servingData(); + + /** + * Creates a new builder for constructing RequestInfo instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_RequestInfo.Builder(); + } + + /** Builder for constructing RequestInfo instances. */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + /** - * An opaque string that should only be interpreted by the service that - * generated it. For example, it can be used to identify requests in the - * service's logs. - * - *

This identifier is typically used by service operators and support - * teams to trace the execution of specific requests through the system. - * - * @return the request identifier + * Sets the request identifier. + * + * @param requestId the request identifier + * @return this builder for method chaining */ @JsonProperty("request_id") - public abstract String requestId(); + public abstract Builder requestId(String requestId); /** - * Any data that was used to serve this request. For example, an encrypted - * stack trace that can be sent back to the service provider for debugging. - * - *

This field may contain sensitive information that is useful for - * debugging but should be handled carefully. It might include: - *

    - *
  • Stack traces or error logs
  • - *
  • Request context or parameters
  • - *
  • System state information
  • - *
  • Other diagnostic data
  • - *
- * - * @return data that was used to serve the request + * Sets the serving data. + * + * @param servingData data that was used to serve the request + * @return this builder for method chaining */ @JsonProperty("serving_data") - public abstract String servingData(); - - /** - * Creates a new builder for constructing RequestInfo instances. - * - * @return a new builder instance - */ - public static Builder builder() { - return new AutoValue_RequestInfo.Builder(); - } + public abstract Builder servingData(String servingData); /** - * Builder for constructing RequestInfo instances. + * Builds the RequestInfo instance. + * + * @return a new RequestInfo instance */ - @AutoValue.Builder - @JsonIgnoreProperties(ignoreUnknown = true) - public abstract static class Builder { - - /** - * Sets the request identifier. - * - * @param requestId the request identifier - * @return this builder for method chaining - */ - @JsonProperty("request_id") - public abstract Builder requestId(String requestId); - - /** - * Sets the serving data. - * - * @param servingData data that was used to serve the request - * @return this builder for method chaining - */ - @JsonProperty("serving_data") - public abstract Builder servingData(String servingData); - - /** - * Builds the RequestInfo instance. - * - * @return a new RequestInfo instance - */ - public abstract RequestInfo build(); - } -} \ No newline at end of file + public abstract RequestInfo build(); + } +} diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ResourceInfo.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ResourceInfo.java index 731eb6db4..8338cb5b4 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ResourceInfo.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ResourceInfo.java @@ -1,32 +1,34 @@ package com.databricks.sdk.core.error.details; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.auto.value.AutoValue; /** * ResourceInfo describes the resource that is being accessed. - * - *

This error detail type provides information about the specific resource - * that was involved in the error. This is particularly useful when the error - * is related to resource access, permissions, or resource-specific operations. - * + * + *

This error detail type provides information about the specific resource that was involved in + * the error. This is particularly useful when the error is related to resource access, permissions, + * or resource-specific operations. + * *

ResourceInfo helps clients understand: + * *

    - *
  • What resource was involved in the error
  • - *
  • What type of resource it was
  • - *
  • Who owns the resource (if applicable)
  • - *
  • What specific error occurred when accessing the resource
  • + *
  • What resource was involved in the error + *
  • What type of resource it was + *
  • Who owns the resource (if applicable) + *
  • What specific error occurred when accessing the resource *
- * + * *

Examples of when ResourceInfo might be provided: + * *

    - *
  • Permission denied errors
  • - *
  • Resource not found errors
  • - *
  • Resource conflict errors
  • - *
  • Resource quota exceeded errors
  • - *
  • Resource state validation errors
  • + *
  • Permission denied errors + *
  • Resource not found errors + *
  • Resource conflict errors + *
  • Resource quota exceeded errors + *
  • Resource state validation errors *
*/ @AutoValue @@ -34,156 +36,154 @@ @JsonIgnoreProperties(ignoreUnknown = true) public abstract class ResourceInfo { + /** + * A name for the type of resource being accessed. + * + *

This field categorizes the resource by its type or class. Examples might include: + * + *

    + *
  • "workspace" + *
  • "cluster" + *
  • "job" + *
  • "table" + *
  • "user" + *
  • "group" + *
  • "secret" + *
  • "notebook" + *
+ * + *

This information helps clients understand what category of resource was involved and + * potentially implement resource-type-specific error handling. + * + * @return the type of resource + */ + @JsonProperty("resource_type") + public abstract String resourceType(); + + /** + * The name of the resource being accessed. + * + *

This field provides the specific identifier or name of the resource that was involved in the + * error. The format depends on the resource type but typically includes: + * + *

    + *
  • Resource IDs (e.g., "12345") + *
  • Resource names (e.g., "my-cluster") + *
  • Resource paths (e.g., "/path/to/resource") + *
  • Resource URIs (e.g., "databricks://workspace/123") + *
+ * + *

This identifier helps clients locate the specific resource and potentially retry the + * operation or report the issue more precisely. + * + * @return the name or identifier of the resource + */ + @JsonProperty("resource_name") + public abstract String resourceName(); + + /** + * The owner of the resource (optional). + * + *

This field identifies who owns or controls the resource. This information is particularly + * useful for permission-related errors or when the client needs to contact the resource owner. + * + *

Examples of owner values: + * + *

    + *
  • User email addresses + *
  • User IDs + *
  • Group names + *
  • Service account identifiers + *
  • Organization names + *
+ * + *

Note: This field may be null or empty if the owner information is not available or not + * applicable. + * + * @return the owner of the resource, or null if not available + */ + @JsonProperty("owner") + public abstract String owner(); + + /** + * Describes what error is encountered when accessing this resource. + * + *

This field provides a human-readable description of the specific error that occurred when + * trying to access or operate on the resource. The description should be clear enough for + * developers to understand what went wrong. + * + *

Examples of resource error descriptions: + * + *

    + *
  • "Resource not found" + *
  • "Access denied: insufficient permissions" + *
  • "Resource is currently locked by another operation" + *
  • "Resource quota exceeded" + *
  • "Resource is in an invalid state for this operation" + *
  • "Resource name contains invalid characters" + *
  • "Resource already exists with this name" + *
+ * + * @return description of the resource access error + */ + @JsonProperty("description") + public abstract String description(); + + /** + * Creates a new builder for constructing ResourceInfo instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_ResourceInfo.Builder(); + } + + /** Builder for constructing ResourceInfo instances. */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + /** - * A name for the type of resource being accessed. - * - *

This field categorizes the resource by its type or class. Examples - * might include: - *

    - *
  • "workspace"
  • - *
  • "cluster"
  • - *
  • "job"
  • - *
  • "table"
  • - *
  • "user"
  • - *
  • "group"
  • - *
  • "secret"
  • - *
  • "notebook"
  • - *
- * - *

This information helps clients understand what category of resource - * was involved and potentially implement resource-type-specific error handling. - * - * @return the type of resource + * Sets the resource type. + * + * @param resourceType the type of resource + * @return this builder for method chaining */ @JsonProperty("resource_type") - public abstract String resourceType(); + public abstract Builder resourceType(String resourceType); /** - * The name of the resource being accessed. - * - *

This field provides the specific identifier or name of the resource - * that was involved in the error. The format depends on the resource type - * but typically includes: - *

    - *
  • Resource IDs (e.g., "12345")
  • - *
  • Resource names (e.g., "my-cluster")
  • - *
  • Resource paths (e.g., "/path/to/resource")
  • - *
  • Resource URIs (e.g., "databricks://workspace/123")
  • - *
- * - *

This identifier helps clients locate the specific resource and - * potentially retry the operation or report the issue more precisely. - * - * @return the name or identifier of the resource + * Sets the resource name. + * + * @param resourceName the name or identifier of the resource + * @return this builder for method chaining */ @JsonProperty("resource_name") - public abstract String resourceName(); + public abstract Builder resourceName(String resourceName); /** - * The owner of the resource (optional). - * - *

This field identifies who owns or controls the resource. This - * information is particularly useful for permission-related errors or - * when the client needs to contact the resource owner. - * - *

Examples of owner values: - *

    - *
  • User email addresses
  • - *
  • User IDs
  • - *
  • Group names
  • - *
  • Service account identifiers
  • - *
  • Organization names
  • - *
- * - *

Note: This field may be null or empty if the owner information - * is not available or not applicable. - * - * @return the owner of the resource, or null if not available + * Sets the resource owner. + * + * @param owner the owner of the resource + * @return this builder for method chaining */ @JsonProperty("owner") - public abstract String owner(); + public abstract Builder owner(String owner); /** - * Describes what error is encountered when accessing this resource. - * - *

This field provides a human-readable description of the specific - * error that occurred when trying to access or operate on the resource. - * The description should be clear enough for developers to understand - * what went wrong. - * - *

Examples of resource error descriptions: - *

    - *
  • "Resource not found"
  • - *
  • "Access denied: insufficient permissions"
  • - *
  • "Resource is currently locked by another operation"
  • - *
  • "Resource quota exceeded"
  • - *
  • "Resource is in an invalid state for this operation"
  • - *
  • "Resource name contains invalid characters"
  • - *
  • "Resource already exists with this name"
  • - *
- * - * @return description of the resource access error + * Sets the error description. + * + * @param description description of the resource access error + * @return this builder for method chaining */ @JsonProperty("description") - public abstract String description(); - - /** - * Creates a new builder for constructing ResourceInfo instances. - * - * @return a new builder instance - */ - public static Builder builder() { - return new AutoValue_ResourceInfo.Builder(); - } + public abstract Builder description(String description); /** - * Builder for constructing ResourceInfo instances. + * Builds the ResourceInfo instance. + * + * @return a new ResourceInfo instance */ - @AutoValue.Builder - @JsonIgnoreProperties(ignoreUnknown = true) - public abstract static class Builder { - - /** - * Sets the resource type. - * - * @param resourceType the type of resource - * @return this builder for method chaining - */ - @JsonProperty("resource_type") - public abstract Builder resourceType(String resourceType); - - /** - * Sets the resource name. - * - * @param resourceName the name or identifier of the resource - * @return this builder for method chaining - */ - @JsonProperty("resource_name") - public abstract Builder resourceName(String resourceName); - - /** - * Sets the resource owner. - * - * @param owner the owner of the resource - * @return this builder for method chaining - */ - @JsonProperty("owner") - public abstract Builder owner(String owner); - - /** - * Sets the error description. - * - * @param description description of the resource access error - * @return this builder for method chaining - */ - @JsonProperty("description") - public abstract Builder description(String description); - - /** - * Builds the ResourceInfo instance. - * - * @return a new ResourceInfo instance - */ - public abstract ResourceInfo build(); - } -} \ No newline at end of file + public abstract ResourceInfo build(); + } +} diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RetryInfo.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RetryInfo.java index 0a1d55fee..a8394fd4c 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RetryInfo.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RetryInfo.java @@ -1,130 +1,128 @@ package com.databricks.sdk.core.error.details; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.auto.value.AutoValue; import java.io.IOException; import java.time.Duration; /** * RetryInfo describes when clients can retry a failed request. - * - *

Clients could ignore the recommendation here or retry when this information - * is missing from error responses. However, it's always recommended that clients - * should use exponential backoff when retrying. - * - *

Clients should wait until the {@code retryDelay} amount of time has passed - * since receiving the error response before retrying. If retrying requests also - * fail, clients should use an exponential backoff scheme to gradually increase - * the delay between retries based on {@code retryDelay}, until either a maximum - * number of retries have been reached or a maximum retry delay cap has been reached. - * - *

This information helps clients implement intelligent retry strategies that - * respect the service's recommendations while avoiding overwhelming the service - * with rapid retry attempts. + * + *

Clients could ignore the recommendation here or retry when this information is missing from + * error responses. However, it's always recommended that clients should use exponential backoff + * when retrying. + * + *

Clients should wait until the {@code retryDelay} amount of time has passed since receiving the + * error response before retrying. If retrying requests also fail, clients should use an exponential + * backoff scheme to gradually increase the delay between retries based on {@code retryDelay}, until + * either a maximum number of retries have been reached or a maximum retry delay cap has been + * reached. + * + *

This information helps clients implement intelligent retry strategies that respect the + * service's recommendations while avoiding overwhelming the service with rapid retry attempts. */ @AutoValue @JsonDeserialize(builder = AutoValue_RetryInfo.Builder.class) @JsonIgnoreProperties(ignoreUnknown = true) public abstract class RetryInfo { - + + /** + * Clients should wait at least this long between retrying the same request. + * + *

Note: This is serialized as a string in the format "3.000000001s" where the string ends in + * the suffix "s" (indicating seconds) and is preceded by a decimal number of seconds. + * + *

Examples of valid formats: + * + *

    + *
  • "30s" - 30 seconds + *
  • "1.5s" - 1.5 seconds + *
  • "0.001s" - 1 millisecond + *
  • "3.000000001s" - 3 seconds and 1 nanosecond + *
+ * + * @return the recommended delay before retrying + */ + @JsonProperty("retry_delay") + @JsonDeserialize(using = RetryInfo.DurationDeserializer.class) + public abstract Duration retryDelay(); + + /** + * Creates a new builder for constructing RetryInfo instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_RetryInfo.Builder(); + } + + /** Builder for constructing RetryInfo instances. */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + /** - * Clients should wait at least this long between retrying the same request. - * - *

Note: This is serialized as a string in the format "3.000000001s" where - * the string ends in the suffix "s" (indicating seconds) and is preceded by - * a decimal number of seconds. - * - *

Examples of valid formats: - *

    - *
  • "30s" - 30 seconds
  • - *
  • "1.5s" - 1.5 seconds
  • - *
  • "0.001s" - 1 millisecond
  • - *
  • "3.000000001s" - 3 seconds and 1 nanosecond
  • - *
- * - * @return the recommended delay before retrying + * Sets the retry delay. + * + * @param retryDelay the recommended delay before retrying + * @return this builder for method chaining */ @JsonProperty("retry_delay") @JsonDeserialize(using = RetryInfo.DurationDeserializer.class) - public abstract Duration retryDelay(); - - /** - * Creates a new builder for constructing RetryInfo instances. - * - * @return a new builder instance - */ - public static Builder builder() { - return new AutoValue_RetryInfo.Builder(); - } - - /** - * Builder for constructing RetryInfo instances. - */ - @AutoValue.Builder - @JsonIgnoreProperties(ignoreUnknown = true) - public abstract static class Builder { - - /** - * Sets the retry delay. - * - * @param retryDelay the recommended delay before retrying - * @return this builder for method chaining - */ - @JsonProperty("retry_delay") - @JsonDeserialize(using = RetryInfo.DurationDeserializer.class) - public abstract Builder retryDelay(Duration retryDelay); - - /** - * Builds the RetryInfo instance. - * - * @return a new RetryInfo instance - */ - public abstract RetryInfo build(); - } - + public abstract Builder retryDelay(Duration retryDelay); + /** - * Custom deserializer for Duration field to handle "30s" format. - * - *

This deserializer is strict and will reject invalid duration formats - * by throwing an IOException. Valid formats must: - *

    - *
  • Be a string value
  • - *
  • End with the 's' suffix
  • - *
  • Contain a valid decimal number before the suffix
  • - *
- * - *

Examples of valid formats: "30s", "1.5s", "0.001s", "3.000000001s" - * Examples of invalid formats: "30", "30ms", "abc", 42 + * Builds the RetryInfo instance. + * + * @return a new RetryInfo instance */ - static class DurationDeserializer extends JsonDeserializer { - @Override - public Duration deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - JsonNode node = p.getCodec().readTree(p); - - if (!node.isTextual()) { - throw new IOException("Duration must be a string, got: " + node.getNodeType()); - } - - // Parse duration string like "1.000000001s" or "30s" - String delayStr = node.asText(); - if (!delayStr.endsWith("s")) { - throw new IOException("Duration must end with 's' suffix: " + delayStr); - } - - try { - String secondsStr = delayStr.substring(0, delayStr.length() - 1); - double seconds = Double.parseDouble(secondsStr); - long nanos = (long) (seconds * 1_000_000_000); - return Duration.ofNanos(nanos); - } catch (NumberFormatException e) { - throw new IOException("Invalid duration format: " + delayStr, e); - } - } + public abstract RetryInfo build(); + } + + /** + * Custom deserializer for Duration field to handle "30s" format. + * + *

This deserializer is strict and will reject invalid duration formats by throwing an + * IOException. Valid formats must: + * + *

    + *
  • Be a string value + *
  • End with the 's' suffix + *
  • Contain a valid decimal number before the suffix + *
+ * + *

Examples of valid formats: "30s", "1.5s", "0.001s", "3.000000001s" Examples of invalid + * formats: "30", "30ms", "abc", 42 + */ + static class DurationDeserializer extends JsonDeserializer { + @Override + public Duration deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + JsonNode node = p.getCodec().readTree(p); + + if (!node.isTextual()) { + throw new IOException("Duration must be a string, got: " + node.getNodeType()); + } + + // Parse duration string like "1.000000001s" or "30s" + String delayStr = node.asText(); + if (!delayStr.endsWith("s")) { + throw new IOException("Duration must end with 's' suffix: " + delayStr); + } + + try { + String secondsStr = delayStr.substring(0, delayStr.length() - 1); + double seconds = Double.parseDouble(secondsStr); + long nanos = (long) (seconds * 1_000_000_000); + return Duration.ofNanos(nanos); + } catch (NumberFormatException e) { + throw new IOException("Invalid duration format: " + delayStr, e); + } } -} \ No newline at end of file + } +} diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/utils/SerDeUtils.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/utils/SerDeUtils.java index be55ab0db..8fd484996 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/utils/SerDeUtils.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/utils/SerDeUtils.java @@ -4,8 +4,8 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.guava.GuavaModule; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; /** Utilities for serialization and deserialization in the Databricks Java SDK. */ public class SerDeUtils { 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 64b8d7f31..15080ccf7 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 @@ -319,21 +319,20 @@ void errorDetails() throws JsonProcessingException { Map metadata = new HashMap<>(); metadata.put("etag", "value"); - + // Create ErrorDetails object instead of List - ErrorDetails errorDetails = ErrorDetails.builder() - .setErrorInfo(ErrorInfo.builder() - .reason("reason") - .domain("domain") - .metadata(metadata) - .build()) - .setUnknownDetails(Arrays.asList( - mapper.createObjectNode() - .put("@type", "unrelated") - .put("reason", "wrong") - .put("domain", "wrongDomain") - )) - .build(); + ErrorDetails errorDetails = + ErrorDetails.builder() + .setErrorInfo( + ErrorInfo.builder().reason("reason").domain("domain").metadata(metadata).build()) + .setUnknownDetails( + Arrays.asList( + mapper + .createObjectNode() + .put("@type", "unrelated") + .put("reason", "wrong") + .put("domain", "wrongDomain"))) + .build(); DatabricksError error = runFailingApiClientTest( @@ -342,14 +341,7 @@ void errorDetails() throws JsonProcessingException { getTransientError( req, 401, - new ApiErrorBody( - "ERROR", - null, - null, - null, - null, - null, - errorDetails)), + new ApiErrorBody("ERROR", null, null, null, null, null, errorDetails)), getSuccessResponse(req)), MyEndpointResponse.class, DatabricksError.class); diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java index 9deed8234..55ea346e5 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java @@ -1,402 +1,409 @@ package com.databricks.sdk.core.error.details; -import com.fasterxml.jackson.databind.ObjectMapper; +import static org.junit.jupiter.api.Assertions.*; + import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.guava.GuavaModule; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; public class ErrorDetailsTest { - - private final ObjectMapper mapper = new ObjectMapper() - .registerModule(new GuavaModule()); - - @Test - public void testDeserializeFromArray() throws Exception { - String json = "[" + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + - "\"reason\": \"PERMISSION_DENIED\"," + - "\"domain\": \"databricks.com\"," + - "\"metadata\": {" + - "\"service\": \"workspace\"" + - "}" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.RequestInfo\"," + - "\"request_id\": \"req-123\"," + - "\"serving_data\": \"stack-trace-data\"" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.RetryInfo\"," + - "\"retry_delay\": \"30s\"" + - "}," + - "{" + - "\"@type\": \"unknown.type\"," + - "\"custom_field\": \"custom_value\"" + - "}" + - "]"; - - ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); - - assertTrue(errorDetails.errorInfo().isPresent()); - assertEquals("PERMISSION_DENIED", errorDetails.errorInfo().get().reason()); - assertEquals("databricks.com", errorDetails.errorInfo().get().domain()); - - assertTrue(errorDetails.requestInfo().isPresent()); - assertEquals("req-123", errorDetails.requestInfo().get().requestId()); - assertEquals("stack-trace-data", errorDetails.requestInfo().get().servingData()); - - assertTrue(errorDetails.retryInfo().isPresent()); - assertEquals("PT30S", errorDetails.retryInfo().get().retryDelay().toString()); - - assertEquals(1, errorDetails.unknownDetails().size()); - JsonNode unknownDetail = errorDetails.unknownDetails().get(0); - assertEquals("unknown.type", unknownDetail.get("@type").asText()); - assertEquals("custom_value", unknownDetail.get("custom_field").asText()); - } - - @Test - public void testDeserializeFromSingleObject() throws Exception { - String json = "{" + - "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + - "\"reason\": \"RESOURCE_EXHAUSTED\"," + - "\"domain\": \"databricks.com\"," + - "\"metadata\": {" + - "\"quota_limit\": \"1000\"" + - "}" + - "}"; - - ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); - - assertTrue(errorDetails.errorInfo().isPresent()); - assertEquals("RESOURCE_EXHAUSTED", errorDetails.errorInfo().get().reason()); - assertEquals("databricks.com", errorDetails.errorInfo().get().domain()); - assertEquals("1000", errorDetails.errorInfo().get().metadata().get("quota_limit")); - - assertEquals(0, errorDetails.unknownDetails().size()); - } - - @Test - public void testDeserializeFromArrayWithMixedTypes() throws Exception { - String json = "[" + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.QuotaFailure\"," + - "\"violations\": [" + - "{" + - "\"subject\": \"project:123\"," + - "\"description\": \"Daily limit exceeded\"" + - "}" + - "]" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.Help\"," + - "\"links\": [" + - "{" + - "\"description\": \"Quota documentation\"," + - "\"url\": \"https://docs.databricks.com/quota\"" + - "}" + - "]" + - "}," + - "{" + - "\"unrecognized_field\": \"value\"" + - "}" + - "]"; - - ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); - - assertTrue(errorDetails.quotaFailure().isPresent()); - assertEquals(1, errorDetails.quotaFailure().get().violations().size()); - assertEquals("project:123", errorDetails.quotaFailure().get().violations().get(0).subject()); - assertEquals("Daily limit exceeded", errorDetails.quotaFailure().get().violations().get(0).description()); - - assertTrue(errorDetails.help().isPresent()); - assertEquals(1, errorDetails.help().get().links().size()); - assertEquals("Quota documentation", errorDetails.help().get().links().get(0).description()); - assertEquals("https://docs.databricks.com/quota", errorDetails.help().get().links().get(0).url()); - - assertEquals(1, errorDetails.unknownDetails().size()); - JsonNode unknownDetail = errorDetails.unknownDetails().get(0); - assertEquals("value", unknownDetail.get("unrecognized_field").asText()); - } - - @Test - public void testDeserializeEmptyArray() throws Exception { - String json = "[]"; - - ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); - - assertFalse(errorDetails.errorInfo().isPresent()); - assertFalse(errorDetails.requestInfo().isPresent()); - assertFalse(errorDetails.retryInfo().isPresent()); - assertFalse(errorDetails.debugInfo().isPresent()); - assertFalse(errorDetails.quotaFailure().isPresent()); - assertFalse(errorDetails.preconditionFailure().isPresent()); - assertFalse(errorDetails.badRequest().isPresent()); - assertFalse(errorDetails.resourceInfo().isPresent()); - assertFalse(errorDetails.help().isPresent()); - assertEquals(0, errorDetails.unknownDetails().size()); - } - - @Test - public void testDeserializeAllErrorDetailTypes() throws Exception { - String json = "[" + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + - "\"reason\": \"reason\"," + - "\"domain\": \"domain\"," + - "\"metadata\": {\"k1\": \"v1\", \"k2\": \"v2\"}" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.RequestInfo\"," + - "\"request_id\": \"req42\"," + - "\"serving_data\": \"data\"" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.RetryInfo\"," + - "\"retry_delay\": \"1.000000001s\"" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.DebugInfo\"," + - "\"stack_entries\": [\"entry1\", \"entry2\"]," + - "\"detail\": \"detail\"" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.QuotaFailure\"," + - "\"violations\": [{\"subject\": \"subject\", \"description\": \"description\"}]" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.PreconditionFailure\"," + - "\"violations\": [{\"type\": \"type\", \"subject\": \"subject\", \"description\": \"description\"}]" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.BadRequest\"," + - "\"field_violations\": [{\"field\": \"field\", \"description\": \"description\"}]" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.ResourceInfo\"," + - "\"resource_type\": \"resource_type\"," + - "\"resource_name\": \"resource_name\"," + - "\"owner\": \"owner\"," + - "\"description\": \"description\"" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.Help\"," + - "\"links\": [{\"description\": \"description\", \"url\": \"url\"}]" + - "}" + - "]"; - - ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); - - // Verify all error detail types are present - assertTrue(errorDetails.errorInfo().isPresent()); - assertEquals("reason", errorDetails.errorInfo().get().reason()); - assertEquals("domain", errorDetails.errorInfo().get().domain()); - assertEquals("v1", errorDetails.errorInfo().get().metadata().get("k1")); - assertEquals("v2", errorDetails.errorInfo().get().metadata().get("k2")); - - assertTrue(errorDetails.requestInfo().isPresent()); - assertEquals("req42", errorDetails.requestInfo().get().requestId()); - assertEquals("data", errorDetails.requestInfo().get().servingData()); - - assertTrue(errorDetails.retryInfo().isPresent()); - assertEquals("PT1.000000001S", errorDetails.retryInfo().get().retryDelay().toString()); - - assertTrue(errorDetails.debugInfo().isPresent()); - assertEquals(2, errorDetails.debugInfo().get().stackEntries().size()); - assertEquals("entry1", errorDetails.debugInfo().get().stackEntries().get(0)); - assertEquals("entry2", errorDetails.debugInfo().get().stackEntries().get(1)); - assertEquals("detail", errorDetails.debugInfo().get().detail()); - - assertTrue(errorDetails.quotaFailure().isPresent()); - assertEquals(1, errorDetails.quotaFailure().get().violations().size()); - assertEquals("subject", errorDetails.quotaFailure().get().violations().get(0).subject()); - assertEquals("description", errorDetails.quotaFailure().get().violations().get(0).description()); - - assertTrue(errorDetails.preconditionFailure().isPresent()); - assertEquals(1, errorDetails.preconditionFailure().get().violations().size()); - assertEquals("type", errorDetails.preconditionFailure().get().violations().get(0).type()); - assertEquals("subject", errorDetails.preconditionFailure().get().violations().get(0).subject()); - assertEquals("description", errorDetails.preconditionFailure().get().violations().get(0).description()); - - assertTrue(errorDetails.badRequest().isPresent()); - assertEquals(1, errorDetails.badRequest().get().fieldViolations().size()); - assertEquals("field", errorDetails.badRequest().get().fieldViolations().get(0).field()); - assertEquals("description", errorDetails.badRequest().get().fieldViolations().get(0).description()); - - assertTrue(errorDetails.resourceInfo().isPresent()); - assertEquals("resource_type", errorDetails.resourceInfo().get().resourceType()); - assertEquals("resource_name", errorDetails.resourceInfo().get().resourceName()); - assertEquals("owner", errorDetails.resourceInfo().get().owner()); - assertEquals("description", errorDetails.resourceInfo().get().description()); - - assertTrue(errorDetails.help().isPresent()); - assertEquals(1, errorDetails.help().get().links().size()); - assertEquals("description", errorDetails.help().get().links().get(0).description()); - assertEquals("url", errorDetails.help().get().links().get(0).url()); - - assertEquals(0, errorDetails.unknownDetails().size()); - } - - @Test - public void testDeserializeUnknownErrorDetailType() throws Exception { - String json = "[" + - "{" + - "\"@type\": \"foo\"," + - "\"reason\": \"reason\"" + - "}" + - "]"; - - ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); - - // Verify no known error detail types are present. - assertFalse(errorDetails.errorInfo().isPresent()); - assertFalse(errorDetails.requestInfo().isPresent()); - assertFalse(errorDetails.retryInfo().isPresent()); - assertFalse(errorDetails.debugInfo().isPresent()); - assertFalse(errorDetails.quotaFailure().isPresent()); - assertFalse(errorDetails.preconditionFailure().isPresent()); - assertFalse(errorDetails.badRequest().isPresent()); - assertFalse(errorDetails.resourceInfo().isPresent()); - assertFalse(errorDetails.help().isPresent()); - - // Verify unknown detail is captured. - assertEquals(1, errorDetails.unknownDetails().size()); - JsonNode unknownDetail = errorDetails.unknownDetails().get(0); - assertEquals("foo", unknownDetail.get("@type").asText()); - assertEquals("reason", unknownDetail.get("reason").asText()); - } - - @Test - public void testDeserializeInvalidErrorDetails() throws Exception { - String json = "[" + - "42," + - "\"foobar\"," + - "{\"foo\": \"bar\"}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + - "\"reason\": 0" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.RequestInfo\"," + - "\"request_id\": 0" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.RetryInfo\"," + - "\"retry_delay\": 0" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.DebugInfo\"," + - "\"stack_entries\": 0" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.QuotaFailure\"," + - "\"violations\": 0" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.PreconditionFailure\"," + - "\"violations\": 0" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.BadRequest\"," + - "\"field_violations\": 0" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.ResourceInfo\"," + - "\"resource_type\": 0" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.Help\"," + - "\"links\": 0" + - "}" + - "]"; - - ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); - - // All error detail types should fail to deserialize due to invalid data - // and be captured as unknown details. - assertFalse(errorDetails.errorInfo().isPresent()); - assertFalse(errorDetails.badRequest().isPresent()); - assertFalse(errorDetails.help().isPresent()); - assertFalse(errorDetails.requestInfo().isPresent()); - assertFalse(errorDetails.resourceInfo().isPresent()); - assertFalse(errorDetails.quotaFailure().isPresent()); - assertFalse(errorDetails.preconditionFailure().isPresent()); - assertFalse(errorDetails.debugInfo().isPresent()); - assertFalse(errorDetails.retryInfo().isPresent()); - - // 12 items should be captured as unknown details: - // - 3 non-error detail types (42, "foobar", {"foo": "bar"}), - // - 9 error detail objects with invalid data (including RetryInfo). - assertEquals(12, errorDetails.unknownDetails().size()); - - // Verify the first three unknown details (non-error detail types). - JsonNode firstUnknown = errorDetails.unknownDetails().get(0); - assertTrue(firstUnknown.isNumber()); - assertEquals(42, firstUnknown.asInt()); - - JsonNode secondUnknown = errorDetails.unknownDetails().get(1); - assertTrue(secondUnknown.isTextual()); - assertEquals("foobar", secondUnknown.asText()); - - JsonNode thirdUnknown = errorDetails.unknownDetails().get(2); - assertTrue(thirdUnknown.isObject()); - assertEquals("bar", thirdUnknown.get("foo").asText()); - - // Verify that error detail objects with invalid data are in unknown details - // Check for a few key types to ensure they're properly captured. - boolean foundErrorInfo = false; - boolean foundBadRequest = false; - for (JsonNode unknownDetail : errorDetails.unknownDetails()) { - if (unknownDetail.isObject() && unknownDetail.has("@type")) { - String type = unknownDetail.get("@type").asText(); - if ("type.googleapis.com/google.rpc.ErrorInfo".equals(type)) { - assertEquals(0, unknownDetail.get("reason").asInt()); - foundErrorInfo = true; - } else if ("type.googleapis.com/google.rpc.BadRequest".equals(type)) { - assertEquals(0, unknownDetail.get("field_violations").asInt()); - foundBadRequest = true; - } - } + + private final ObjectMapper mapper = new ObjectMapper().registerModule(new GuavaModule()); + + @Test + public void testDeserializeFromArray() throws Exception { + String json = + "[" + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + + "\"reason\": \"PERMISSION_DENIED\"," + + "\"domain\": \"databricks.com\"," + + "\"metadata\": {" + + "\"service\": \"workspace\"" + + "}" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.RequestInfo\"," + + "\"request_id\": \"req-123\"," + + "\"serving_data\": \"stack-trace-data\"" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.RetryInfo\"," + + "\"retry_delay\": \"30s\"" + + "}," + + "{" + + "\"@type\": \"unknown.type\"," + + "\"custom_field\": \"custom_value\"" + + "}" + + "]"; + + ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); + + assertTrue(errorDetails.errorInfo().isPresent()); + assertEquals("PERMISSION_DENIED", errorDetails.errorInfo().get().reason()); + assertEquals("databricks.com", errorDetails.errorInfo().get().domain()); + + assertTrue(errorDetails.requestInfo().isPresent()); + assertEquals("req-123", errorDetails.requestInfo().get().requestId()); + assertEquals("stack-trace-data", errorDetails.requestInfo().get().servingData()); + + assertTrue(errorDetails.retryInfo().isPresent()); + assertEquals("PT30S", errorDetails.retryInfo().get().retryDelay().toString()); + + assertEquals(1, errorDetails.unknownDetails().size()); + JsonNode unknownDetail = errorDetails.unknownDetails().get(0); + assertEquals("unknown.type", unknownDetail.get("@type").asText()); + assertEquals("custom_value", unknownDetail.get("custom_field").asText()); + } + + @Test + public void testDeserializeFromSingleObject() throws Exception { + String json = + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + + "\"reason\": \"RESOURCE_EXHAUSTED\"," + + "\"domain\": \"databricks.com\"," + + "\"metadata\": {" + + "\"quota_limit\": \"1000\"" + + "}" + + "}"; + + ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); + + assertTrue(errorDetails.errorInfo().isPresent()); + assertEquals("RESOURCE_EXHAUSTED", errorDetails.errorInfo().get().reason()); + assertEquals("databricks.com", errorDetails.errorInfo().get().domain()); + assertEquals("1000", errorDetails.errorInfo().get().metadata().get("quota_limit")); + + assertEquals(0, errorDetails.unknownDetails().size()); + } + + @Test + public void testDeserializeFromArrayWithMixedTypes() throws Exception { + String json = + "[" + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.QuotaFailure\"," + + "\"violations\": [" + + "{" + + "\"subject\": \"project:123\"," + + "\"description\": \"Daily limit exceeded\"" + + "}" + + "]" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.Help\"," + + "\"links\": [" + + "{" + + "\"description\": \"Quota documentation\"," + + "\"url\": \"https://docs.databricks.com/quota\"" + + "}" + + "]" + + "}," + + "{" + + "\"unrecognized_field\": \"value\"" + + "}" + + "]"; + + ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); + + assertTrue(errorDetails.quotaFailure().isPresent()); + assertEquals(1, errorDetails.quotaFailure().get().violations().size()); + assertEquals("project:123", errorDetails.quotaFailure().get().violations().get(0).subject()); + assertEquals( + "Daily limit exceeded", + errorDetails.quotaFailure().get().violations().get(0).description()); + + assertTrue(errorDetails.help().isPresent()); + assertEquals(1, errorDetails.help().get().links().size()); + assertEquals("Quota documentation", errorDetails.help().get().links().get(0).description()); + assertEquals( + "https://docs.databricks.com/quota", errorDetails.help().get().links().get(0).url()); + + assertEquals(1, errorDetails.unknownDetails().size()); + JsonNode unknownDetail = errorDetails.unknownDetails().get(0); + assertEquals("value", unknownDetail.get("unrecognized_field").asText()); + } + + @Test + public void testDeserializeEmptyArray() throws Exception { + String json = "[]"; + + ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); + + assertFalse(errorDetails.errorInfo().isPresent()); + assertFalse(errorDetails.requestInfo().isPresent()); + assertFalse(errorDetails.retryInfo().isPresent()); + assertFalse(errorDetails.debugInfo().isPresent()); + assertFalse(errorDetails.quotaFailure().isPresent()); + assertFalse(errorDetails.preconditionFailure().isPresent()); + assertFalse(errorDetails.badRequest().isPresent()); + assertFalse(errorDetails.resourceInfo().isPresent()); + assertFalse(errorDetails.help().isPresent()); + assertEquals(0, errorDetails.unknownDetails().size()); + } + + @Test + public void testDeserializeAllErrorDetailTypes() throws Exception { + String json = + "[" + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + + "\"reason\": \"reason\"," + + "\"domain\": \"domain\"," + + "\"metadata\": {\"k1\": \"v1\", \"k2\": \"v2\"}" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.RequestInfo\"," + + "\"request_id\": \"req42\"," + + "\"serving_data\": \"data\"" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.RetryInfo\"," + + "\"retry_delay\": \"1.000000001s\"" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.DebugInfo\"," + + "\"stack_entries\": [\"entry1\", \"entry2\"]," + + "\"detail\": \"detail\"" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.QuotaFailure\"," + + "\"violations\": [{\"subject\": \"subject\", \"description\": \"description\"}]" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.PreconditionFailure\"," + + "\"violations\": [{\"type\": \"type\", \"subject\": \"subject\", \"description\": \"description\"}]" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.BadRequest\"," + + "\"field_violations\": [{\"field\": \"field\", \"description\": \"description\"}]" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.ResourceInfo\"," + + "\"resource_type\": \"resource_type\"," + + "\"resource_name\": \"resource_name\"," + + "\"owner\": \"owner\"," + + "\"description\": \"description\"" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.Help\"," + + "\"links\": [{\"description\": \"description\", \"url\": \"url\"}]" + + "}" + + "]"; + + ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); + + // Verify all error detail types are present + assertTrue(errorDetails.errorInfo().isPresent()); + assertEquals("reason", errorDetails.errorInfo().get().reason()); + assertEquals("domain", errorDetails.errorInfo().get().domain()); + assertEquals("v1", errorDetails.errorInfo().get().metadata().get("k1")); + assertEquals("v2", errorDetails.errorInfo().get().metadata().get("k2")); + + assertTrue(errorDetails.requestInfo().isPresent()); + assertEquals("req42", errorDetails.requestInfo().get().requestId()); + assertEquals("data", errorDetails.requestInfo().get().servingData()); + + assertTrue(errorDetails.retryInfo().isPresent()); + assertEquals("PT1.000000001S", errorDetails.retryInfo().get().retryDelay().toString()); + + assertTrue(errorDetails.debugInfo().isPresent()); + assertEquals(2, errorDetails.debugInfo().get().stackEntries().size()); + assertEquals("entry1", errorDetails.debugInfo().get().stackEntries().get(0)); + assertEquals("entry2", errorDetails.debugInfo().get().stackEntries().get(1)); + assertEquals("detail", errorDetails.debugInfo().get().detail()); + + assertTrue(errorDetails.quotaFailure().isPresent()); + assertEquals(1, errorDetails.quotaFailure().get().violations().size()); + assertEquals("subject", errorDetails.quotaFailure().get().violations().get(0).subject()); + assertEquals( + "description", errorDetails.quotaFailure().get().violations().get(0).description()); + + assertTrue(errorDetails.preconditionFailure().isPresent()); + assertEquals(1, errorDetails.preconditionFailure().get().violations().size()); + assertEquals("type", errorDetails.preconditionFailure().get().violations().get(0).type()); + assertEquals("subject", errorDetails.preconditionFailure().get().violations().get(0).subject()); + assertEquals( + "description", errorDetails.preconditionFailure().get().violations().get(0).description()); + + assertTrue(errorDetails.badRequest().isPresent()); + assertEquals(1, errorDetails.badRequest().get().fieldViolations().size()); + assertEquals("field", errorDetails.badRequest().get().fieldViolations().get(0).field()); + assertEquals( + "description", errorDetails.badRequest().get().fieldViolations().get(0).description()); + + assertTrue(errorDetails.resourceInfo().isPresent()); + assertEquals("resource_type", errorDetails.resourceInfo().get().resourceType()); + assertEquals("resource_name", errorDetails.resourceInfo().get().resourceName()); + assertEquals("owner", errorDetails.resourceInfo().get().owner()); + assertEquals("description", errorDetails.resourceInfo().get().description()); + + assertTrue(errorDetails.help().isPresent()); + assertEquals(1, errorDetails.help().get().links().size()); + assertEquals("description", errorDetails.help().get().links().get(0).description()); + assertEquals("url", errorDetails.help().get().links().get(0).url()); + + assertEquals(0, errorDetails.unknownDetails().size()); + } + + @Test + public void testDeserializeUnknownErrorDetailType() throws Exception { + String json = "[" + "{" + "\"@type\": \"foo\"," + "\"reason\": \"reason\"" + "}" + "]"; + + ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); + + // Verify no known error detail types are present. + assertFalse(errorDetails.errorInfo().isPresent()); + assertFalse(errorDetails.requestInfo().isPresent()); + assertFalse(errorDetails.retryInfo().isPresent()); + assertFalse(errorDetails.debugInfo().isPresent()); + assertFalse(errorDetails.quotaFailure().isPresent()); + assertFalse(errorDetails.preconditionFailure().isPresent()); + assertFalse(errorDetails.badRequest().isPresent()); + assertFalse(errorDetails.resourceInfo().isPresent()); + assertFalse(errorDetails.help().isPresent()); + + // Verify unknown detail is captured. + assertEquals(1, errorDetails.unknownDetails().size()); + JsonNode unknownDetail = errorDetails.unknownDetails().get(0); + assertEquals("foo", unknownDetail.get("@type").asText()); + assertEquals("reason", unknownDetail.get("reason").asText()); + } + + @Test + public void testDeserializeInvalidErrorDetails() throws Exception { + String json = + "[" + + "42," + + "\"foobar\"," + + "{\"foo\": \"bar\"}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + + "\"reason\": 0" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.RequestInfo\"," + + "\"request_id\": 0" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.RetryInfo\"," + + "\"retry_delay\": 0" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.DebugInfo\"," + + "\"stack_entries\": 0" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.QuotaFailure\"," + + "\"violations\": 0" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.PreconditionFailure\"," + + "\"violations\": 0" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.BadRequest\"," + + "\"field_violations\": 0" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.ResourceInfo\"," + + "\"resource_type\": 0" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.Help\"," + + "\"links\": 0" + + "}" + + "]"; + + ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); + + // All error detail types should fail to deserialize due to invalid data + // and be captured as unknown details. + assertFalse(errorDetails.errorInfo().isPresent()); + assertFalse(errorDetails.badRequest().isPresent()); + assertFalse(errorDetails.help().isPresent()); + assertFalse(errorDetails.requestInfo().isPresent()); + assertFalse(errorDetails.resourceInfo().isPresent()); + assertFalse(errorDetails.quotaFailure().isPresent()); + assertFalse(errorDetails.preconditionFailure().isPresent()); + assertFalse(errorDetails.debugInfo().isPresent()); + assertFalse(errorDetails.retryInfo().isPresent()); + + // 12 items should be captured as unknown details: + // - 3 non-error detail types (42, "foobar", {"foo": "bar"}), + // - 9 error detail objects with invalid data (including RetryInfo). + assertEquals(12, errorDetails.unknownDetails().size()); + + // Verify the first three unknown details (non-error detail types). + JsonNode firstUnknown = errorDetails.unknownDetails().get(0); + assertTrue(firstUnknown.isNumber()); + assertEquals(42, firstUnknown.asInt()); + + JsonNode secondUnknown = errorDetails.unknownDetails().get(1); + assertTrue(secondUnknown.isTextual()); + assertEquals("foobar", secondUnknown.asText()); + + JsonNode thirdUnknown = errorDetails.unknownDetails().get(2); + assertTrue(thirdUnknown.isObject()); + assertEquals("bar", thirdUnknown.get("foo").asText()); + + // Verify that error detail objects with invalid data are in unknown details + // Check for a few key types to ensure they're properly captured. + boolean foundErrorInfo = false; + boolean foundBadRequest = false; + for (JsonNode unknownDetail : errorDetails.unknownDetails()) { + if (unknownDetail.isObject() && unknownDetail.has("@type")) { + String type = unknownDetail.get("@type").asText(); + if ("type.googleapis.com/google.rpc.ErrorInfo".equals(type)) { + assertEquals(0, unknownDetail.get("reason").asInt()); + foundErrorInfo = true; + } else if ("type.googleapis.com/google.rpc.BadRequest".equals(type)) { + assertEquals(0, unknownDetail.get("field_violations").asInt()); + foundBadRequest = true; } - - assertTrue(foundErrorInfo, "ErrorInfo should be found in unknown details"); - assertTrue(foundBadRequest, "BadRequest should be found in unknown details"); + } } - - @Test - public void testDeserializeLastErrorDetailWins() throws Exception { - String json = "[" + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + - "\"reason\": \"first\"," + - "\"domain\": \"test\"," + - "\"metadata\": {}" + - "}," + - "{" + - "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + - "\"reason\": \"second\"," + - "\"domain\": \"test\"," + - "\"metadata\": {}" + - "}" + - "]"; - - ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); - - // Verify only the last ErrorInfo is present. - assertTrue(errorDetails.errorInfo().isPresent()); - assertEquals("second", errorDetails.errorInfo().get().reason()); - - // Verify no other error detail types are present. - assertFalse(errorDetails.requestInfo().isPresent()); - assertFalse(errorDetails.retryInfo().isPresent()); - assertFalse(errorDetails.debugInfo().isPresent()); - assertFalse(errorDetails.quotaFailure().isPresent()); - assertFalse(errorDetails.preconditionFailure().isPresent()); - assertFalse(errorDetails.badRequest().isPresent()); - assertFalse(errorDetails.resourceInfo().isPresent()); - assertFalse(errorDetails.help().isPresent()); - - assertEquals(0, errorDetails.unknownDetails().size()); - } -} \ No newline at end of file + + assertTrue(foundErrorInfo, "ErrorInfo should be found in unknown details"); + assertTrue(foundBadRequest, "BadRequest should be found in unknown details"); + } + + @Test + public void testDeserializeLastErrorDetailWins() throws Exception { + String json = + "[" + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + + "\"reason\": \"first\"," + + "\"domain\": \"test\"," + + "\"metadata\": {}" + + "}," + + "{" + + "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + + "\"reason\": \"second\"," + + "\"domain\": \"test\"," + + "\"metadata\": {}" + + "}" + + "]"; + + ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); + + // Verify only the last ErrorInfo is present. + assertTrue(errorDetails.errorInfo().isPresent()); + assertEquals("second", errorDetails.errorInfo().get().reason()); + + // Verify no other error detail types are present. + assertFalse(errorDetails.requestInfo().isPresent()); + assertFalse(errorDetails.retryInfo().isPresent()); + assertFalse(errorDetails.debugInfo().isPresent()); + assertFalse(errorDetails.quotaFailure().isPresent()); + assertFalse(errorDetails.preconditionFailure().isPresent()); + assertFalse(errorDetails.badRequest().isPresent()); + assertFalse(errorDetails.resourceInfo().isPresent()); + assertFalse(errorDetails.help().isPresent()); + + assertEquals(0, errorDetails.unknownDetails().size()); + } +} From b05e7ed22d56ef2b099eeb06e5a86e1d21eb9481 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Mon, 11 Aug 2025 20:30:39 +0000 Subject: [PATCH 03/14] Better formatting --- .../core/error/details/ErrorDetailsTest.java | 287 +++++++++--------- 1 file changed, 144 insertions(+), 143 deletions(-) diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java index 55ea346e5..e93c9434a 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java @@ -15,27 +15,27 @@ public class ErrorDetailsTest { public void testDeserializeFromArray() throws Exception { String json = "[" - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," - + "\"reason\": \"PERMISSION_DENIED\"," - + "\"domain\": \"databricks.com\"," - + "\"metadata\": {" - + "\"service\": \"workspace\"" - + "}" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.RequestInfo\"," - + "\"request_id\": \"req-123\"," - + "\"serving_data\": \"stack-trace-data\"" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.RetryInfo\"," - + "\"retry_delay\": \"30s\"" - + "}," - + "{" - + "\"@type\": \"unknown.type\"," - + "\"custom_field\": \"custom_value\"" - + "}" + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + + " \"reason\": \"PERMISSION_DENIED\"," + + " \"domain\": \"databricks.com\"," + + " \"metadata\": {" + + " \"service\": \"workspace\"" + + " }" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.RequestInfo\"," + + " \"request_id\": \"req-123\"," + + " \"serving_data\": \"stack-trace-data\"" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.RetryInfo\"," + + " \"retry_delay\": \"30s\"" + + " }," + + " {" + + " \"@type\": \"unknown.type\"," + + " \"custom_field\": \"custom_value\"" + + " }" + "]"; ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); @@ -61,12 +61,12 @@ public void testDeserializeFromArray() throws Exception { public void testDeserializeFromSingleObject() throws Exception { String json = "{" - + "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," - + "\"reason\": \"RESOURCE_EXHAUSTED\"," - + "\"domain\": \"databricks.com\"," - + "\"metadata\": {" - + "\"quota_limit\": \"1000\"" - + "}" + + " \"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + + " \"reason\": \"RESOURCE_EXHAUSTED\"," + + " \"domain\": \"databricks.com\"," + + " \"metadata\": {" + + " \"quota_limit\": \"1000\"" + + " }" + "}"; ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); @@ -83,27 +83,27 @@ public void testDeserializeFromSingleObject() throws Exception { public void testDeserializeFromArrayWithMixedTypes() throws Exception { String json = "[" - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.QuotaFailure\"," - + "\"violations\": [" - + "{" - + "\"subject\": \"project:123\"," - + "\"description\": \"Daily limit exceeded\"" - + "}" - + "]" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.Help\"," - + "\"links\": [" - + "{" - + "\"description\": \"Quota documentation\"," - + "\"url\": \"https://docs.databricks.com/quota\"" - + "}" - + "]" - + "}," - + "{" - + "\"unrecognized_field\": \"value\"" - + "}" + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.QuotaFailure\"," + + " \"violations\": [" + + " {" + + " \"subject\": \"project:123\"," + + " \"description\": \"Daily limit exceeded\"" + + " }" + + " ]" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.Help\"," + + " \"links\": [" + + " {" + + " \"description\": \"Quota documentation\"," + + " \"url\": \"https://docs.databricks.com/quota\"" + + " }" + + " ]" + + " }," + + " {" + + " \"unrecognized_field\": \"value\"" + + " }" + "]"; ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); @@ -148,49 +148,49 @@ public void testDeserializeEmptyArray() throws Exception { public void testDeserializeAllErrorDetailTypes() throws Exception { String json = "[" - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," - + "\"reason\": \"reason\"," - + "\"domain\": \"domain\"," - + "\"metadata\": {\"k1\": \"v1\", \"k2\": \"v2\"}" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.RequestInfo\"," - + "\"request_id\": \"req42\"," - + "\"serving_data\": \"data\"" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.RetryInfo\"," - + "\"retry_delay\": \"1.000000001s\"" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.DebugInfo\"," - + "\"stack_entries\": [\"entry1\", \"entry2\"]," - + "\"detail\": \"detail\"" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.QuotaFailure\"," - + "\"violations\": [{\"subject\": \"subject\", \"description\": \"description\"}]" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.PreconditionFailure\"," - + "\"violations\": [{\"type\": \"type\", \"subject\": \"subject\", \"description\": \"description\"}]" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.BadRequest\"," - + "\"field_violations\": [{\"field\": \"field\", \"description\": \"description\"}]" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.ResourceInfo\"," - + "\"resource_type\": \"resource_type\"," - + "\"resource_name\": \"resource_name\"," - + "\"owner\": \"owner\"," - + "\"description\": \"description\"" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.Help\"," - + "\"links\": [{\"description\": \"description\", \"url\": \"url\"}]" - + "}" + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + + " \"reason\": \"reason\"," + + " \"domain\": \"domain\"," + + " \"metadata\": {\"k1\": \"v1\", \"k2\": \"v2\"}" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.RequestInfo\"," + + " \"request_id\": \"req42\"," + + " \"serving_data\": \"data\"" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.RetryInfo\"," + + " \"retry_delay\": \"1.000000001s\"" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.DebugInfo\"," + + " \"stack_entries\": [\"entry1\", \"entry2\"]," + + " \"detail\": \"detail\"" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.QuotaFailure\"," + + " \"violations\": [{\"subject\": \"subject\", \"description\": \"description\"}]" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.PreconditionFailure\"," + + " \"violations\": [{\"type\": \"type\", \"subject\": \"subject\", \"description\": \"description\"}]" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.BadRequest\"," + + " \"field_violations\": [{\"field\": \"field\", \"description\": \"description\"}]" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.ResourceInfo\"," + + " \"resource_type\": \"resource_type\"," + + " \"resource_name\": \"resource_name\"," + + " \"owner\": \"owner\"," + + " \"description\": \"description\"" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.Help\"," + + " \"links\": [{\"description\": \"description\", \"url\": \"url\"}]" + + " }" + "]"; ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); @@ -250,7 +250,8 @@ public void testDeserializeAllErrorDetailTypes() throws Exception { @Test public void testDeserializeUnknownErrorDetailType() throws Exception { - String json = "[" + "{" + "\"@type\": \"foo\"," + "\"reason\": \"reason\"" + "}" + "]"; + String json = + "[" + " {" + " \"@type\": \"foo\"," + " \"reason\": \"reason\"" + " }" + "]"; ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); @@ -276,45 +277,45 @@ public void testDeserializeUnknownErrorDetailType() throws Exception { public void testDeserializeInvalidErrorDetails() throws Exception { String json = "[" - + "42," - + "\"foobar\"," - + "{\"foo\": \"bar\"}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," - + "\"reason\": 0" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.RequestInfo\"," - + "\"request_id\": 0" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.RetryInfo\"," - + "\"retry_delay\": 0" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.DebugInfo\"," - + "\"stack_entries\": 0" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.QuotaFailure\"," - + "\"violations\": 0" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.PreconditionFailure\"," - + "\"violations\": 0" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.BadRequest\"," - + "\"field_violations\": 0" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.ResourceInfo\"," - + "\"resource_type\": 0" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.Help\"," - + "\"links\": 0" - + "}" + + " 42," + + " \"foobar\"," + + " {\"foo\": \"bar\"}," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + + " \"reason\": 0" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.RequestInfo\"," + + " \"request_id\": 0" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.RetryInfo\"," + + " \"retry_delay\": 0" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.DebugInfo\"," + + " \"stack_entries\": 0" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.QuotaFailure\"," + + " \"violations\": 0" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.PreconditionFailure\"," + + " \"violations\": 0" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.BadRequest\"," + + " \"field_violations\": 0" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.ResourceInfo\"," + + " \"resource_type\": 0" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.Help\"," + + " \"links\": 0" + + " }" + "]"; ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); @@ -374,18 +375,18 @@ public void testDeserializeInvalidErrorDetails() throws Exception { public void testDeserializeLastErrorDetailWins() throws Exception { String json = "[" - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," - + "\"reason\": \"first\"," - + "\"domain\": \"test\"," - + "\"metadata\": {}" - + "}," - + "{" - + "\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," - + "\"reason\": \"second\"," - + "\"domain\": \"test\"," - + "\"metadata\": {}" - + "}" + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + + " \"reason\": \"first\"," + + " \"domain\": \"test\"," + + " \"metadata\": {}" + + " }," + + " {" + + " \"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\"," + + " \"reason\": \"second\"," + + " \"domain\": \"test\"," + + " \"metadata\": {}" + + " }" + "]"; ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); From ebc0a137c6ae58e46c96abc3c40c4b55e1403596 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 12 Aug 2025 14:20:29 +0000 Subject: [PATCH 04/14] Update setters --- .../com/databricks/sdk/core/error/details/BadRequest.java | 6 +++--- .../com/databricks/sdk/core/error/details/DebugInfo.java | 4 ++-- .../com/databricks/sdk/core/error/details/ErrorInfo.java | 6 +++--- .../java/com/databricks/sdk/core/error/details/Help.java | 6 +++--- .../sdk/core/error/details/PreconditionFailure.java | 8 ++++---- .../databricks/sdk/core/error/details/QuotaFailure.java | 6 +++--- .../databricks/sdk/core/error/details/RequestInfo.java | 4 ++-- .../databricks/sdk/core/error/details/ResourceInfo.java | 8 ++++---- .../com/databricks/sdk/core/error/details/RetryInfo.java | 2 +- 9 files changed, 25 insertions(+), 25 deletions(-) diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/BadRequest.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/BadRequest.java index e7b279d7c..0f958dd64 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/BadRequest.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/BadRequest.java @@ -71,7 +71,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("field_violations") - public abstract Builder fieldViolations(List fieldViolations); + public abstract Builder setFieldViolations(List fieldViolations); /** * Builds the BadRequest instance. @@ -160,7 +160,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("field") - public abstract Builder field(String field); + public abstract Builder setField(String field); /** * Sets the violation description. @@ -169,7 +169,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("description") - public abstract Builder description(String description); + public abstract Builder setDescription(String description); /** * Builds the BadRequestFieldViolation instance. diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/DebugInfo.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/DebugInfo.java index 43f7beb48..66a746b7b 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/DebugInfo.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/DebugInfo.java @@ -91,7 +91,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("stack_entries") - public abstract Builder stackEntries(List stackEntries); + public abstract Builder setStackEntries(List stackEntries); /** * Sets the additional debugging information. @@ -100,7 +100,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("detail") - public abstract Builder detail(String detail); + public abstract Builder setDetail(String detail); /** * Builds the DebugInfo instance. diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorInfo.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorInfo.java index 06be58e82..f62ab3507 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorInfo.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorInfo.java @@ -85,7 +85,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("reason") - public abstract Builder reason(String reason); + public abstract Builder setReason(String reason); /** * Sets the domain of the error. @@ -94,7 +94,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("domain") - public abstract Builder domain(String domain); + public abstract Builder setDomain(String domain); /** * Sets the additional metadata about the error. @@ -103,7 +103,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("metadata") - public abstract Builder metadata(Map metadata); + public abstract Builder setMetadata(Map metadata); /** * Builds the ErrorInfo instance. diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/Help.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/Help.java index 4b412ba9a..3509d21cf 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/Help.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/Help.java @@ -76,7 +76,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("links") - public abstract Builder links(List links); + public abstract Builder setLinks(List links); /** * Builds the Help instance. @@ -179,7 +179,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("description") - public abstract Builder description(String description); + public abstract Builder setDescription(String description); /** * Sets the link URL. @@ -188,7 +188,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("url") - public abstract Builder url(String url); + public abstract Builder setUrl(String url); /** * Builds the HelpLink instance. diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/PreconditionFailure.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/PreconditionFailure.java index fd026877e..c70269fbe 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/PreconditionFailure.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/PreconditionFailure.java @@ -69,7 +69,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("violations") - public abstract Builder violations(List violations); + public abstract Builder setViolations(List violations); /** * Builds the PreconditionFailure instance. @@ -169,7 +169,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("type") - public abstract Builder type(String type); + public abstract Builder setType(String type); /** * Sets the subject of the precondition failure. @@ -178,7 +178,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("subject") - public abstract Builder subject(String subject); + public abstract Builder setSubject(String subject); /** * Sets the description of the precondition failure. @@ -187,7 +187,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("description") - public abstract Builder description(String description); + public abstract Builder setDescription(String description); /** * Builds the PreconditionFailureViolation instance. diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/QuotaFailure.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/QuotaFailure.java index 8ff6c80c5..0935bc682 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/QuotaFailure.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/QuotaFailure.java @@ -63,7 +63,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("violations") - public abstract Builder violations(List violations); + public abstract Builder setViolations(List violations); /** * Builds the QuotaFailure instance. @@ -142,7 +142,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("subject") - public abstract Builder subject(String subject); + public abstract Builder setSubject(String subject); /** * Sets the description of the quota violation. @@ -151,7 +151,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("description") - public abstract Builder description(String description); + public abstract Builder setDescription(String description); /** * Builds the QuotaFailureViolation instance. diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RequestInfo.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RequestInfo.java index 63a1c59cf..d691d2195 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RequestInfo.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RequestInfo.java @@ -78,7 +78,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("request_id") - public abstract Builder requestId(String requestId); + public abstract Builder setRequestId(String requestId); /** * Sets the serving data. @@ -87,7 +87,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("serving_data") - public abstract Builder servingData(String servingData); + public abstract Builder setServingData(String servingData); /** * Builds the RequestInfo instance. diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ResourceInfo.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ResourceInfo.java index 8338cb5b4..f47375b7a 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ResourceInfo.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ResourceInfo.java @@ -150,7 +150,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("resource_type") - public abstract Builder resourceType(String resourceType); + public abstract Builder setResourceType(String resourceType); /** * Sets the resource name. @@ -159,7 +159,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("resource_name") - public abstract Builder resourceName(String resourceName); + public abstract Builder setResourceName(String resourceName); /** * Sets the resource owner. @@ -168,7 +168,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("owner") - public abstract Builder owner(String owner); + public abstract Builder setOwner(String owner); /** * Sets the error description. @@ -177,7 +177,7 @@ public abstract static class Builder { * @return this builder for method chaining */ @JsonProperty("description") - public abstract Builder description(String description); + public abstract Builder setDescription(String description); /** * Builds the ResourceInfo instance. diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RetryInfo.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RetryInfo.java index a8394fd4c..1ca357990 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RetryInfo.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RetryInfo.java @@ -75,7 +75,7 @@ public abstract static class Builder { */ @JsonProperty("retry_delay") @JsonDeserialize(using = RetryInfo.DurationDeserializer.class) - public abstract Builder retryDelay(Duration retryDelay); + public abstract Builder setRetryDelay(Duration retryDelay); /** * Builds the RetryInfo instance. From 187ac13fe953052914e17b775d2688a4e2aa9676 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 12 Aug 2025 16:59:20 +0000 Subject: [PATCH 05/14] Fix test --- .../src/test/java/com/databricks/sdk/core/ApiClientTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 15080ccf7..4d3089324 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 @@ -324,7 +324,7 @@ void errorDetails() throws JsonProcessingException { ErrorDetails errorDetails = ErrorDetails.builder() .setErrorInfo( - ErrorInfo.builder().reason("reason").domain("domain").metadata(metadata).build()) + ErrorInfo.builder().setReason("reason").setDomain("domain").setMetadata(metadata).build()) .setUnknownDetails( Arrays.asList( mapper From 2ce262703c2323080f6989631368e31e6f1da0ee Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 12 Aug 2025 17:52:52 +0000 Subject: [PATCH 06/14] fmt --- .../test/java/com/databricks/sdk/core/ApiClientTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 4d3089324..ef51f8363 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 @@ -324,7 +324,11 @@ void errorDetails() throws JsonProcessingException { ErrorDetails errorDetails = ErrorDetails.builder() .setErrorInfo( - ErrorInfo.builder().setReason("reason").setDomain("domain").setMetadata(metadata).build()) + ErrorInfo.builder() + .setReason("reason") + .setDomain("domain") + .setMetadata(metadata) + .build()) .setUnknownDetails( Arrays.asList( mapper From 4f9fadf383de8e18b51ab0aefd1a59917c32dc43 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Tue, 12 Aug 2025 18:08:08 +0000 Subject: [PATCH 07/14] No null collections --- .../sdk/core/error/details/BadRequest.java | 12 +++++++++++- .../databricks/sdk/core/error/details/DebugInfo.java | 12 +++++++++++- .../databricks/sdk/core/error/details/ErrorInfo.java | 12 +++++++++++- .../com/databricks/sdk/core/error/details/Help.java | 12 +++++++++++- .../sdk/core/error/details/PreconditionFailure.java | 12 +++++++++++- .../sdk/core/error/details/QuotaFailure.java | 12 +++++++++++- 6 files changed, 66 insertions(+), 6 deletions(-) diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/BadRequest.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/BadRequest.java index 0f958dd64..3bae9cd32 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/BadRequest.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/BadRequest.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.auto.value.AutoValue; +import java.util.Collections; import java.util.List; /** @@ -78,7 +79,16 @@ public abstract static class Builder { * * @return a new BadRequest instance */ - public abstract BadRequest build(); + public BadRequest build() { + if (fieldViolations() == null) { + setFieldViolations(Collections.emptyList()); + } + return autoBuild(); + } + + abstract List fieldViolations(); + + abstract BadRequest autoBuild(); } /** diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/DebugInfo.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/DebugInfo.java index 66a746b7b..d13c87cc3 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/DebugInfo.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/DebugInfo.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.auto.value.AutoValue; +import java.util.Collections; import java.util.List; /** @@ -107,6 +108,15 @@ public abstract static class Builder { * * @return a new DebugInfo instance */ - public abstract DebugInfo build(); + public DebugInfo build() { + if (stackEntries() == null) { + setStackEntries(Collections.emptyList()); + } + return autoBuild(); + } + + abstract List stackEntries(); + + abstract DebugInfo autoBuild(); } } diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorInfo.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorInfo.java index f62ab3507..53c23d270 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorInfo.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorInfo.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.auto.value.AutoValue; +import java.util.Collections; import java.util.Map; /** @@ -110,6 +111,15 @@ public abstract static class Builder { * * @return a new ErrorInfo instance */ - public abstract ErrorInfo build(); + public ErrorInfo build() { + if (metadata() == null) { + setMetadata(Collections.emptyMap()); + } + return autoBuild(); + } + + abstract Map metadata(); + + abstract ErrorInfo autoBuild(); } } diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/Help.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/Help.java index 3509d21cf..f631803c2 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/Help.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/Help.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.auto.value.AutoValue; +import java.util.Collections; import java.util.List; /** @@ -83,7 +84,16 @@ public abstract static class Builder { * * @return a new Help instance */ - public abstract Help build(); + public Help build() { + if (links() == null) { + setLinks(Collections.emptyList()); + } + return autoBuild(); + } + + abstract List links(); + + abstract Help autoBuild(); } /** diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/PreconditionFailure.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/PreconditionFailure.java index c70269fbe..ca5a16692 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/PreconditionFailure.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/PreconditionFailure.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.auto.value.AutoValue; +import java.util.Collections; import java.util.List; /** @@ -76,7 +77,16 @@ public abstract static class Builder { * * @return a new PreconditionFailure instance */ - public abstract PreconditionFailure build(); + public PreconditionFailure build() { + if (violations() == null) { + setViolations(Collections.emptyList()); + } + return autoBuild(); + } + + abstract List violations(); + + abstract PreconditionFailure autoBuild(); } /** diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/QuotaFailure.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/QuotaFailure.java index 0935bc682..1e5c2fa22 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/QuotaFailure.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/QuotaFailure.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.google.auto.value.AutoValue; +import java.util.Collections; import java.util.List; /** @@ -70,7 +71,16 @@ public abstract static class Builder { * * @return a new QuotaFailure instance */ - public abstract QuotaFailure build(); + public QuotaFailure build() { + if (violations() == null) { + setViolations(Collections.emptyList()); + } + return autoBuild(); + } + + abstract List violations(); + + abstract QuotaFailure autoBuild(); } /** From 0a16bf2ab2f9a080defe5c765aa39b65e883629a Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 13 Aug 2025 05:57:40 +0000 Subject: [PATCH 08/14] Handle PR comments --- .../sdk/core/error/details/ErrorDetails.java | 4 + .../sdk/core/error/details/RetryInfo.java | 27 +- .../core/error/details/ErrorDetailsTest.java | 364 ++++++++++-------- 3 files changed, 228 insertions(+), 167 deletions(-) diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java index 3bd82c786..10d681243 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java @@ -7,6 +7,10 @@ import java.util.List; import java.util.Optional; +/** + * ErrorDetails contains the error details of an API error. It is the union of known error details + * types and unknown details. + */ @AutoValue @JsonDeserialize(using = ErrorDetailsDeserializer.class) public abstract class ErrorDetails { diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RetryInfo.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RetryInfo.java index 1ca357990..a44d9a141 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RetryInfo.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/RetryInfo.java @@ -109,17 +109,34 @@ public Duration deserialize(JsonParser p, DeserializationContext ctxt) throws IO throw new IOException("Duration must be a string, got: " + node.getNodeType()); } - // Parse duration string like "1.000000001s" or "30s" String delayStr = node.asText(); if (!delayStr.endsWith("s")) { throw new IOException("Duration must end with 's' suffix: " + delayStr); } + String numeric = delayStr.substring(0, delayStr.length() - 1).trim(); + if (numeric.isEmpty()) { + throw new IOException("Invalid duration format: " + delayStr); + } + if (numeric.startsWith("-")) { + throw new IOException("Duration must be non-negative: " + delayStr); + } + + int dotIdx = numeric.indexOf('.'); + String secondsPart = dotIdx >= 0 ? numeric.substring(0, dotIdx) : numeric; + String fractionPart = dotIdx >= 0 ? numeric.substring(dotIdx + 1) : ""; + + if (fractionPart.length() > 9) { + throw new IOException("Fractional seconds precision exceeds nanoseconds: " + delayStr); + } + + String fractionPadded = + fractionPart.isEmpty() ? "" : (fractionPart + "000000000").substring(0, 9); + try { - String secondsStr = delayStr.substring(0, delayStr.length() - 1); - double seconds = Double.parseDouble(secondsStr); - long nanos = (long) (seconds * 1_000_000_000); - return Duration.ofNanos(nanos); + long seconds = secondsPart.isEmpty() ? 0L : Long.parseLong(secondsPart); + long nanos = fractionPadded.isEmpty() ? 0L : Long.parseLong(fractionPadded); + return Duration.ofSeconds(seconds, nanos); } catch (NumberFormatException e) { throw new IOException("Invalid duration format: " + delayStr, e); } diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java index e93c9434a..904076093 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java @@ -2,14 +2,17 @@ import static org.junit.jupiter.api.Assertions.*; +import com.databricks.sdk.core.utils.SerDeUtils; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.guava.GuavaModule; +import com.google.common.collect.ImmutableMap; +import java.time.Duration; +import java.util.Arrays; import org.junit.jupiter.api.Test; public class ErrorDetailsTest { - private final ObjectMapper mapper = new ObjectMapper().registerModule(new GuavaModule()); + private final ObjectMapper mapper = SerDeUtils.createMapper(); @Test public void testDeserializeFromArray() throws Exception { @@ -40,21 +43,28 @@ public void testDeserializeFromArray() throws Exception { ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); - assertTrue(errorDetails.errorInfo().isPresent()); - assertEquals("PERMISSION_DENIED", errorDetails.errorInfo().get().reason()); - assertEquals("databricks.com", errorDetails.errorInfo().get().domain()); - - assertTrue(errorDetails.requestInfo().isPresent()); - assertEquals("req-123", errorDetails.requestInfo().get().requestId()); - assertEquals("stack-trace-data", errorDetails.requestInfo().get().servingData()); - - assertTrue(errorDetails.retryInfo().isPresent()); - assertEquals("PT30S", errorDetails.retryInfo().get().retryDelay().toString()); - - assertEquals(1, errorDetails.unknownDetails().size()); - JsonNode unknownDetail = errorDetails.unknownDetails().get(0); - assertEquals("unknown.type", unknownDetail.get("@type").asText()); - assertEquals("custom_value", unknownDetail.get("custom_field").asText()); + JsonNode unknownDetail = + mapper.readTree("{\"@type\": \"unknown.type\", \"custom_field\": \"custom_value\"}"); + + ErrorDetails expected = + ErrorDetails.builder() + .setErrorInfo( + ErrorInfo.builder() + .setReason("PERMISSION_DENIED") + .setDomain("databricks.com") + .setMetadata( + ImmutableMap.builder().put("service", "workspace").build()) + .build()) + .setRequestInfo( + RequestInfo.builder() + .setRequestId("req-123") + .setServingData("stack-trace-data") + .build()) + .setRetryInfo(RetryInfo.builder().setRetryDelay(Duration.ofSeconds(30)).build()) + .setUnknownDetails(Arrays.asList(unknownDetail)) + .build(); + + assertEquals(expected, errorDetails); } @Test @@ -71,12 +81,18 @@ public void testDeserializeFromSingleObject() throws Exception { ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); - assertTrue(errorDetails.errorInfo().isPresent()); - assertEquals("RESOURCE_EXHAUSTED", errorDetails.errorInfo().get().reason()); - assertEquals("databricks.com", errorDetails.errorInfo().get().domain()); - assertEquals("1000", errorDetails.errorInfo().get().metadata().get("quota_limit")); - - assertEquals(0, errorDetails.unknownDetails().size()); + ErrorDetails expected = + ErrorDetails.builder() + .setErrorInfo( + ErrorInfo.builder() + .setReason("RESOURCE_EXHAUSTED") + .setDomain("databricks.com") + .setMetadata( + ImmutableMap.builder().put("quota_limit", "1000").build()) + .build()) + .build(); + + assertEquals(expected, errorDetails); } @Test @@ -108,22 +124,32 @@ public void testDeserializeFromArrayWithMixedTypes() throws Exception { ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); - assertTrue(errorDetails.quotaFailure().isPresent()); - assertEquals(1, errorDetails.quotaFailure().get().violations().size()); - assertEquals("project:123", errorDetails.quotaFailure().get().violations().get(0).subject()); - assertEquals( - "Daily limit exceeded", - errorDetails.quotaFailure().get().violations().get(0).description()); - - assertTrue(errorDetails.help().isPresent()); - assertEquals(1, errorDetails.help().get().links().size()); - assertEquals("Quota documentation", errorDetails.help().get().links().get(0).description()); - assertEquals( - "https://docs.databricks.com/quota", errorDetails.help().get().links().get(0).url()); - - assertEquals(1, errorDetails.unknownDetails().size()); - JsonNode unknownDetail = errorDetails.unknownDetails().get(0); - assertEquals("value", unknownDetail.get("unrecognized_field").asText()); + JsonNode unknownDetail = mapper.readTree("{\"unrecognized_field\": \"value\"}"); + + ErrorDetails expected = + ErrorDetails.builder() + .setQuotaFailure( + QuotaFailure.builder() + .setViolations( + Arrays.asList( + QuotaFailure.QuotaFailureViolation.builder() + .setSubject("project:123") + .setDescription("Daily limit exceeded") + .build())) + .build()) + .setHelp( + Help.builder() + .setLinks( + Arrays.asList( + Help.HelpLink.builder() + .setDescription("Quota documentation") + .setUrl("https://docs.databricks.com/quota") + .build())) + .build()) + .setUnknownDetails(Arrays.asList(unknownDetail)) + .build(); + + assertEquals(expected, errorDetails); } @Test @@ -132,16 +158,9 @@ public void testDeserializeEmptyArray() throws Exception { ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); - assertFalse(errorDetails.errorInfo().isPresent()); - assertFalse(errorDetails.requestInfo().isPresent()); - assertFalse(errorDetails.retryInfo().isPresent()); - assertFalse(errorDetails.debugInfo().isPresent()); - assertFalse(errorDetails.quotaFailure().isPresent()); - assertFalse(errorDetails.preconditionFailure().isPresent()); - assertFalse(errorDetails.badRequest().isPresent()); - assertFalse(errorDetails.resourceInfo().isPresent()); - assertFalse(errorDetails.help().isPresent()); - assertEquals(0, errorDetails.unknownDetails().size()); + ErrorDetails expected = ErrorDetails.builder().build(); + + assertEquals(expected, errorDetails); } @Test @@ -195,57 +214,73 @@ public void testDeserializeAllErrorDetailTypes() throws Exception { ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); - // Verify all error detail types are present - assertTrue(errorDetails.errorInfo().isPresent()); - assertEquals("reason", errorDetails.errorInfo().get().reason()); - assertEquals("domain", errorDetails.errorInfo().get().domain()); - assertEquals("v1", errorDetails.errorInfo().get().metadata().get("k1")); - assertEquals("v2", errorDetails.errorInfo().get().metadata().get("k2")); - - assertTrue(errorDetails.requestInfo().isPresent()); - assertEquals("req42", errorDetails.requestInfo().get().requestId()); - assertEquals("data", errorDetails.requestInfo().get().servingData()); - - assertTrue(errorDetails.retryInfo().isPresent()); - assertEquals("PT1.000000001S", errorDetails.retryInfo().get().retryDelay().toString()); - - assertTrue(errorDetails.debugInfo().isPresent()); - assertEquals(2, errorDetails.debugInfo().get().stackEntries().size()); - assertEquals("entry1", errorDetails.debugInfo().get().stackEntries().get(0)); - assertEquals("entry2", errorDetails.debugInfo().get().stackEntries().get(1)); - assertEquals("detail", errorDetails.debugInfo().get().detail()); - - assertTrue(errorDetails.quotaFailure().isPresent()); - assertEquals(1, errorDetails.quotaFailure().get().violations().size()); - assertEquals("subject", errorDetails.quotaFailure().get().violations().get(0).subject()); - assertEquals( - "description", errorDetails.quotaFailure().get().violations().get(0).description()); - - assertTrue(errorDetails.preconditionFailure().isPresent()); - assertEquals(1, errorDetails.preconditionFailure().get().violations().size()); - assertEquals("type", errorDetails.preconditionFailure().get().violations().get(0).type()); - assertEquals("subject", errorDetails.preconditionFailure().get().violations().get(0).subject()); - assertEquals( - "description", errorDetails.preconditionFailure().get().violations().get(0).description()); - - assertTrue(errorDetails.badRequest().isPresent()); - assertEquals(1, errorDetails.badRequest().get().fieldViolations().size()); - assertEquals("field", errorDetails.badRequest().get().fieldViolations().get(0).field()); - assertEquals( - "description", errorDetails.badRequest().get().fieldViolations().get(0).description()); - - assertTrue(errorDetails.resourceInfo().isPresent()); - assertEquals("resource_type", errorDetails.resourceInfo().get().resourceType()); - assertEquals("resource_name", errorDetails.resourceInfo().get().resourceName()); - assertEquals("owner", errorDetails.resourceInfo().get().owner()); - assertEquals("description", errorDetails.resourceInfo().get().description()); - - assertTrue(errorDetails.help().isPresent()); - assertEquals(1, errorDetails.help().get().links().size()); - assertEquals("description", errorDetails.help().get().links().get(0).description()); - assertEquals("url", errorDetails.help().get().links().get(0).url()); - - assertEquals(0, errorDetails.unknownDetails().size()); + ErrorDetails expected = + ErrorDetails.builder() + .setErrorInfo( + ErrorInfo.builder() + .setReason("reason") + .setDomain("domain") + .setMetadata( + ImmutableMap.builder() + .put("k1", "v1") + .put("k2", "v2") + .build()) + .build()) + .setRequestInfo( + RequestInfo.builder().setRequestId("req42").setServingData("data").build()) + .setRetryInfo(RetryInfo.builder().setRetryDelay(Duration.ofSeconds(1, 1)).build()) + .setDebugInfo( + DebugInfo.builder() + .setStackEntries(Arrays.asList("entry1", "entry2")) + .setDetail("detail") + .build()) + .setQuotaFailure( + QuotaFailure.builder() + .setViolations( + Arrays.asList( + QuotaFailure.QuotaFailureViolation.builder() + .setSubject("subject") + .setDescription("description") + .build())) + .build()) + .setPreconditionFailure( + PreconditionFailure.builder() + .setViolations( + Arrays.asList( + PreconditionFailure.PreconditionFailureViolation.builder() + .setType("type") + .setSubject("subject") + .setDescription("description") + .build())) + .build()) + .setBadRequest( + BadRequest.builder() + .setFieldViolations( + Arrays.asList( + BadRequest.BadRequestFieldViolation.builder() + .setField("field") + .setDescription("description") + .build())) + .build()) + .setResourceInfo( + ResourceInfo.builder() + .setResourceType("resource_type") + .setResourceName("resource_name") + .setOwner("owner") + .setDescription("description") + .build()) + .setHelp( + Help.builder() + .setLinks( + Arrays.asList( + Help.HelpLink.builder() + .setDescription("description") + .setUrl("url") + .build())) + .build()) + .build(); + + assertEquals(expected, errorDetails); } @Test @@ -255,22 +290,12 @@ public void testDeserializeUnknownErrorDetailType() throws Exception { ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); - // Verify no known error detail types are present. - assertFalse(errorDetails.errorInfo().isPresent()); - assertFalse(errorDetails.requestInfo().isPresent()); - assertFalse(errorDetails.retryInfo().isPresent()); - assertFalse(errorDetails.debugInfo().isPresent()); - assertFalse(errorDetails.quotaFailure().isPresent()); - assertFalse(errorDetails.preconditionFailure().isPresent()); - assertFalse(errorDetails.badRequest().isPresent()); - assertFalse(errorDetails.resourceInfo().isPresent()); - assertFalse(errorDetails.help().isPresent()); - - // Verify unknown detail is captured. - assertEquals(1, errorDetails.unknownDetails().size()); - JsonNode unknownDetail = errorDetails.unknownDetails().get(0); - assertEquals("foo", unknownDetail.get("@type").asText()); - assertEquals("reason", unknownDetail.get("reason").asText()); + JsonNode unknownDetail = mapper.readTree("{\"@type\": \"foo\", \"reason\": \"reason\"}"); + + ErrorDetails expected = + ErrorDetails.builder().setUnknownDetails(Arrays.asList(unknownDetail)).build(); + + assertEquals(expected, errorDetails); } @Test @@ -320,38 +345,57 @@ public void testDeserializeInvalidErrorDetails() throws Exception { ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); - // All error detail types should fail to deserialize due to invalid data - // and be captured as unknown details. - assertFalse(errorDetails.errorInfo().isPresent()); - assertFalse(errorDetails.badRequest().isPresent()); - assertFalse(errorDetails.help().isPresent()); - assertFalse(errorDetails.requestInfo().isPresent()); - assertFalse(errorDetails.resourceInfo().isPresent()); - assertFalse(errorDetails.quotaFailure().isPresent()); - assertFalse(errorDetails.preconditionFailure().isPresent()); - assertFalse(errorDetails.debugInfo().isPresent()); - assertFalse(errorDetails.retryInfo().isPresent()); - - // 12 items should be captured as unknown details: - // - 3 non-error detail types (42, "foobar", {"foo": "bar"}), - // - 9 error detail objects with invalid data (including RetryInfo). - assertEquals(12, errorDetails.unknownDetails().size()); - - // Verify the first three unknown details (non-error detail types). - JsonNode firstUnknown = errorDetails.unknownDetails().get(0); - assertTrue(firstUnknown.isNumber()); - assertEquals(42, firstUnknown.asInt()); - - JsonNode secondUnknown = errorDetails.unknownDetails().get(1); - assertTrue(secondUnknown.isTextual()); - assertEquals("foobar", secondUnknown.asText()); - - JsonNode thirdUnknown = errorDetails.unknownDetails().get(2); - assertTrue(thirdUnknown.isObject()); - assertEquals("bar", thirdUnknown.get("foo").asText()); - + JsonNode firstUnknown = mapper.readTree("42"); + JsonNode secondUnknown = mapper.readTree("\"foobar\""); + JsonNode thirdUnknown = mapper.readTree("{\"foo\": \"bar\"}"); + JsonNode errorInfoUnknown = + mapper.readTree("{\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\", \"reason\": 0}"); + JsonNode requestInfoUnknown = + mapper.readTree( + "{\"@type\": \"type.googleapis.com/google.rpc.RequestInfo\", \"request_id\": 0}"); + JsonNode retryInfoUnknown = + mapper.readTree( + "{\"@type\": \"type.googleapis.com/google.rpc.RetryInfo\", \"retry_delay\": 0}"); + JsonNode debugInfoUnknown = + mapper.readTree( + "{\"@type\": \"type.googleapis.com/google.rpc.DebugInfo\", \"stack_entries\": 0}"); + JsonNode quotaFailureUnknown = + mapper.readTree( + "{\"@type\": \"type.googleapis.com/google.rpc.QuotaFailure\", \"violations\": 0}"); + JsonNode preconditionFailureUnknown = + mapper.readTree( + "{\"@type\": \"type.googleapis.com/google.rpc.PreconditionFailure\", \"violations\": 0}"); + JsonNode badRequestUnknown = + mapper.readTree( + "{\"@type\": \"type.googleapis.com/google.rpc.BadRequest\", \"field_violations\": 0}"); + JsonNode resourceInfoUnknown = + mapper.readTree( + "{\"@type\": \"type.googleapis.com/google.rpc.ResourceInfo\", \"resource_type\": 0}"); + JsonNode helpUnknown = + mapper.readTree("{\"@type\": \"type.googleapis.com/google.rpc.Help\", \"links\": 0}"); + + ErrorDetails expected = + ErrorDetails.builder() + .setUnknownDetails( + Arrays.asList( + firstUnknown, + secondUnknown, + thirdUnknown, + errorInfoUnknown, + requestInfoUnknown, + retryInfoUnknown, + debugInfoUnknown, + quotaFailureUnknown, + preconditionFailureUnknown, + badRequestUnknown, + resourceInfoUnknown, + helpUnknown)) + .build(); + + assertEquals(expected, errorDetails); + + // Additional validation to ensure specific unknown details are properly captured // Verify that error detail objects with invalid data are in unknown details - // Check for a few key types to ensure they're properly captured. boolean foundErrorInfo = false; boolean foundBadRequest = false; for (JsonNode unknownDetail : errorDetails.unknownDetails()) { @@ -391,20 +435,16 @@ public void testDeserializeLastErrorDetailWins() throws Exception { ErrorDetails errorDetails = mapper.readValue(json, ErrorDetails.class); - // Verify only the last ErrorInfo is present. - assertTrue(errorDetails.errorInfo().isPresent()); - assertEquals("second", errorDetails.errorInfo().get().reason()); - - // Verify no other error detail types are present. - assertFalse(errorDetails.requestInfo().isPresent()); - assertFalse(errorDetails.retryInfo().isPresent()); - assertFalse(errorDetails.debugInfo().isPresent()); - assertFalse(errorDetails.quotaFailure().isPresent()); - assertFalse(errorDetails.preconditionFailure().isPresent()); - assertFalse(errorDetails.badRequest().isPresent()); - assertFalse(errorDetails.resourceInfo().isPresent()); - assertFalse(errorDetails.help().isPresent()); - - assertEquals(0, errorDetails.unknownDetails().size()); + ErrorDetails expected = + ErrorDetails.builder() + .setErrorInfo( + ErrorInfo.builder() + .setReason("second") + .setDomain("test") + .setMetadata(ImmutableMap.of()) + .build()) + .build(); + + assertEquals(expected, errorDetails); } } From 1aa9888f6fd97b05f1211785267b453272f4c253 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 13 Aug 2025 08:09:06 +0000 Subject: [PATCH 09/14] Add comments --- .../java/com/databricks/sdk/core/error/ApiErrorBody.java | 7 +++++++ 1 file changed, 7 insertions(+) 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 fa3fe09e6..01c4edd77 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 @@ -97,6 +97,13 @@ public void setApi12Error(String api12Error) { this.api12Error = api12Error; } + /** + * Converts the error details to a list of ErrorDetail objects. This only supports the ErrorInfo + * type. + * + * @param details The error details to convert. + * @return A list of ErrorDetail objects. + */ private static List fromDetails(ErrorDetails details) { if (details == null) { return Collections.emptyList(); From 16a8b1d5ed3ea19e0b490b276d8cf8199ab030e3 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 13 Aug 2025 08:10:55 +0000 Subject: [PATCH 10/14] Add comments --- .../com/databricks/sdk/core/error/ApiErrorBody.java | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 01c4edd77..f622430dc 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 @@ -24,6 +24,16 @@ public class ApiErrorBody { public ApiErrorBody() {} + /** + * 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, From d016a936e0388ed0b38e0398363a9e8f3998bf77 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 13 Aug 2025 08:14:10 +0000 Subject: [PATCH 11/14] Remove unnecessary checks --- .../core/error/details/ErrorDetailsTest.java | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java index 904076093..4a877a651 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/details/ErrorDetailsTest.java @@ -393,26 +393,6 @@ public void testDeserializeInvalidErrorDetails() throws Exception { .build(); assertEquals(expected, errorDetails); - - // Additional validation to ensure specific unknown details are properly captured - // Verify that error detail objects with invalid data are in unknown details - boolean foundErrorInfo = false; - boolean foundBadRequest = false; - for (JsonNode unknownDetail : errorDetails.unknownDetails()) { - if (unknownDetail.isObject() && unknownDetail.has("@type")) { - String type = unknownDetail.get("@type").asText(); - if ("type.googleapis.com/google.rpc.ErrorInfo".equals(type)) { - assertEquals(0, unknownDetail.get("reason").asInt()); - foundErrorInfo = true; - } else if ("type.googleapis.com/google.rpc.BadRequest".equals(type)) { - assertEquals(0, unknownDetail.get("field_violations").asInt()); - foundBadRequest = true; - } - } - } - - assertTrue(foundErrorInfo, "ErrorInfo should be found in unknown details"); - assertTrue(foundBadRequest, "BadRequest should be found in unknown details"); } @Test From 2d0edf75de7093ea42c511b1345315adc66b6c7b Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 13 Aug 2025 08:24:15 +0000 Subject: [PATCH 12/14] Default to empty list --- .../sdk/core/error/details/ErrorDetails.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java diff --git a/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java b/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java new file mode 100644 index 000000000..cb27ebc8b --- /dev/null +++ b/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java @@ -0,0 +1,75 @@ +package com.databricks.sdk.core.error.details; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * ErrorDetails contains the error details of an API error. It is the union of known error details + * types and unknown details. + */ +@AutoValue +@JsonDeserialize(using = ErrorDetailsDeserializer.class) +public abstract class ErrorDetails { + + public abstract Optional errorInfo(); + + public abstract Optional requestInfo(); + + public abstract Optional retryInfo(); + + public abstract Optional debugInfo(); + + public abstract Optional quotaFailure(); + + public abstract Optional preconditionFailure(); + + public abstract Optional badRequest(); + + public abstract Optional resourceInfo(); + + public abstract Optional help(); + + public abstract List unknownDetails(); + + public static Builder builder() { + return new AutoValue_ErrorDetails.Builder().setUnknownDetails(Collections.emptyList()); + } + + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setErrorInfo(ErrorInfo errorInfo); + + public abstract Builder setRequestInfo(RequestInfo requestInfo); + + public abstract Builder setRetryInfo(RetryInfo retryInfo); + + public abstract Builder setDebugInfo(DebugInfo debugInfo); + + public abstract Builder setQuotaFailure(QuotaFailure quotaFailure); + + public abstract Builder setPreconditionFailure(PreconditionFailure preconditionFailure); + + public abstract Builder setBadRequest(BadRequest badRequest); + + public abstract Builder setResourceInfo(ResourceInfo resourceInfo); + + public abstract Builder setHelp(Help help); + + public abstract Builder setUnknownDetails(List unknownDetails); + + abstract ErrorDetails autoBuild(); + + public ErrorDetails build() { + if (unknownDetails() == null) { + setUnknownDetails(Collections.emptyList()); + } + return autoBuild(); + } + + abstract List unknownDetails(); + } +} \ No newline at end of file From e2dcdadaf5b0cad5e9fc206751e1df09a76ca80b Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 13 Aug 2025 08:40:25 +0000 Subject: [PATCH 13/14] Default to empty list --- .../sdk/core/error/details/ErrorDetails.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java index 10d681243..cd2714aca 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java @@ -36,7 +36,7 @@ public abstract class ErrorDetails { public abstract List unknownDetails(); public static Builder builder() { - return new AutoValue_ErrorDetails.Builder().setUnknownDetails(Collections.emptyList()); + return new AutoValue_ErrorDetails.Builder(); } @AutoValue.Builder @@ -61,6 +61,17 @@ public abstract static class Builder { public abstract Builder setUnknownDetails(List unknownDetails); - public abstract ErrorDetails build(); + abstract List unknownDetails(); + + abstract ErrorDetails autoBuild(); + + public ErrorDetails build() { + try { + unknownDetails(); + } catch (IllegalStateException e) { + setUnknownDetails(Collections.emptyList()); + } + return autoBuild(); + } } } From 730b479e250bce91b6d2a0f90cd753ba7d68ec84 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 13 Aug 2025 08:41:29 +0000 Subject: [PATCH 14/14] fmt --- .../com/databricks/sdk/core/error/details/ErrorDetails.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java b/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java index cb27ebc8b..46e32a1ad 100644 --- a/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java +++ b/src/main/java/com/databricks/sdk/core/error/details/ErrorDetails.java @@ -72,4 +72,4 @@ public ErrorDetails build() { abstract List unknownDetails(); } -} \ No newline at end of file +}