diff --git a/eng/code-quality-reports/src/main/resources/revapi/revapi.json b/eng/code-quality-reports/src/main/resources/revapi/revapi.json index 43617f00c182..73856ee406b3 100644 --- a/eng/code-quality-reports/src/main/resources/revapi/revapi.json +++ b/eng/code-quality-reports/src/main/resources/revapi/revapi.json @@ -633,6 +633,20 @@ "old": "class com.azure.resourcemanager.sql.models.UserIdentity", "new": "class com.azure.resourcemanager.sql.models.UserIdentity", "justification": "Customer unlikely to subclass this class." + }, + { + "ignore": true, + "code": "java.method.parameterTypeChanged", + "old": "parameter void com.azure.storage.blob.models.BlobStorageException::(java.lang.String, com.azure.core.http.HttpResponse, ===java.lang.Object===)", + "new": "parameter void com.azure.storage.blob.models.BlobStorageException::(java.lang.String, com.azure.core.http.HttpResponse, ===com.azure.storage.blob.models.BlobStorageError===)", + "justification": "Previous parameter conveyed no meaning to the customer and this is a type customers aren't expected to instantiate." + }, + { + "ignore": true, + "code": "java.method.parameterTypeChanged", + "old": "parameter void com.azure.storage.file.datalake.models.DataLakeStorageException::(java.lang.String, com.azure.core.http.HttpResponse, ===java.lang.Object===)", + "new": "parameter void com.azure.storage.file.datalake.models.DataLakeStorageException::(java.lang.String, com.azure.core.http.HttpResponse, ===com.azure.storage.file.datalake.models.DataLakeStorageError===)", + "justification": "Previous parameter conveyed no meaning to the customer and this is a type customers aren't expected to instantiate." } ] } diff --git a/sdk/storage/azure-storage-blob-batch/src/main/java/com/azure/storage/blob/batch/BlobBatchHelper.java b/sdk/storage/azure-storage-blob-batch/src/main/java/com/azure/storage/blob/batch/BlobBatchHelper.java index 9f053f17d906..25eaf48375f1 100644 --- a/sdk/storage/azure-storage-blob-batch/src/main/java/com/azure/storage/blob/batch/BlobBatchHelper.java +++ b/sdk/storage/azure-storage-blob-batch/src/main/java/com/azure/storage/blob/batch/BlobBatchHelper.java @@ -3,6 +3,7 @@ package com.azure.storage.blob.batch; +import com.azure.core.http.HttpHeaderName; import com.azure.core.http.HttpHeaders; import com.azure.core.http.HttpRequest; import com.azure.core.http.HttpResponse; @@ -11,11 +12,14 @@ import com.azure.core.util.CoreUtils; import com.azure.core.util.FluxUtil; import com.azure.core.util.logging.ClientLogger; +import com.azure.storage.blob.models.BlobStorageError; import com.azure.storage.blob.models.BlobStorageException; import com.azure.storage.common.implementation.Constants; +import com.azure.xml.XmlReader; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import javax.xml.stream.XMLStreamException; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; @@ -61,7 +65,7 @@ static Mono> mapBatchResponse(BlobBatchOperationInfo batchO * Content-Type will contain the boundary for each batch response. The expected format is: * "Content-Type: multipart/mixed; boundary=batchresponse_66925647-d0cb-4109-b6d3-28efe3e1e5ed" */ - String contentType = rawResponse.getHeaders().getValue(Constants.HeaderConstants.CONTENT_TYPE); + String contentType = rawResponse.getHeaders().getValue(HttpHeaderName.CONTENT_TYPE); // Split on the boundary [ "multipart/mixed; boundary", "batchresponse_66925647-d0cb-4109-b6d3-28efe3e1e5ed"] String[] boundaryPieces = contentType.split("=", 2); @@ -93,9 +97,14 @@ static Mono> mapBatchResponse(BlobBatchOperationInfo batchO int statusCode = getStatusCode(exceptionSections[1], logger); HttpHeaders headers = getHttpHeaders(exceptionSections[1]); - sink.error(logger.logExceptionAsError(new BlobStorageException( - headers.getValue(Constants.HeaderConstants.ERROR_CODE), - createHttpResponse(rawResponse.getRequest(), statusCode, headers, body), body))); + try (XmlReader xmlReader = XmlReader.fromString(exceptionSections[2])) { + sink.error(logger.logExceptionAsError( + new BlobStorageException(headers.getValue(Constants.HeaderConstants.ERROR_CODE), + createHttpResponse(rawResponse.getRequest(), statusCode, headers, body), + BlobStorageError.fromXml(xmlReader)))); + } catch (XMLStreamException ex) { + sink.error(ex); + } } // Split the batch response body into batch operation responses. @@ -123,7 +132,7 @@ static Mono> mapBatchResponse(BlobBatchOperationInfo batchO } } - if (throwOnAnyFailure && exceptions.size() != 0) { + if (throwOnAnyFailure && !exceptions.isEmpty()) { sink.error(logger.logExceptionAsError(new BlobBatchStorageException("Batch had operation failures.", createHttpResponse(rawResponse), exceptions))); } @@ -190,11 +199,15 @@ private static void setBodyOrAddException(BlobBatchOperationResponse batchOpe * Currently, no batching operations will return a success body, they will only return a body on an exception. * For now this will only construct the exception and throw if it should throw on an error. */ - BlobStorageException exception = new BlobStorageException(responseBody, - batchOperationResponse.asHttpResponse(responseBody), responseBody); - logger.logExceptionAsError(exception); - batchOperationResponse.setException(exception); - exceptions.add(exception); + try (XmlReader xmlReader = XmlReader.fromString(responseBody)) { + BlobStorageException exception = new BlobStorageException(responseBody, + batchOperationResponse.asHttpResponse(responseBody), BlobStorageError.fromXml(xmlReader)); + logger.logExceptionAsError(exception); + batchOperationResponse.setException(exception); + exceptions.add(exception); + } catch (XMLStreamException ex) { + throw new RuntimeException(ex); + } } static HttpResponse createHttpResponse(HttpRequest request, int statusCode, HttpHeaders headers, String body) { diff --git a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/models/BlobStorageError.java b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/models/BlobStorageError.java new file mode 100644 index 000000000000..6679b1e07adb --- /dev/null +++ b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/models/BlobStorageError.java @@ -0,0 +1,210 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.storage.blob.models; + +import com.azure.core.util.CoreUtils; +import com.azure.json.JsonReader; +import com.azure.json.JsonSerializable; +import com.azure.json.JsonToken; +import com.azure.json.JsonWriter; +import com.azure.xml.XmlReader; +import com.azure.xml.XmlSerializable; +import com.azure.xml.XmlToken; +import com.azure.xml.XmlWriter; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import java.io.IOException; + +/** + * Represents an error response returned by the Azure Storage Blob service. + */ +public final class BlobStorageError implements JsonSerializable, XmlSerializable { + private String errorCode; + private String message; + private String queryParameterName; + private String queryParameterValue; + private String reason; + private String extendedErrorDetail; + + /** + * Creates an instance of BlobStorageError. + */ + public BlobStorageError() { + } + + /** + * Gets the error code returned by the Azure Storage Blob service. + * + * @return The error code. + */ + public String getErrorCode() { + return errorCode; + } + + /** + * Sets the error code returned by the Azure Storage Blob service. + * + * @param errorCode The error code. + * @return The updated BlobStorageError object. + */ + public BlobStorageError setErrorCode(String errorCode) { + this.errorCode = errorCode; + return this; + } + + /** + * Gets the error message returned by the Azure Storage Blob service. + * + * @return The error message. + */ + public String getMessage() { + return message; + } + + /** + * Sets the error message returned by the Azure Storage Blob service. + * + * @param message The error message. + * @return The updated BlobStorageError object. + */ + public BlobStorageError setMessage(String message) { + this.message = message; + return this; + } + + @Override + public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { + return jsonWriter.writeStartObject("Error") + .writeStringField("Code", errorCode) + .writeStringField("Message", this.message) + .writeStringField("QueryParameterName", this.queryParameterName) + .writeStringField("QueryParameterValue", this.queryParameterValue) + .writeStringField("Reason", this.reason) + .writeStringField("ExtendedErrorDetail", this.extendedErrorDetail) + .writeEndObject(); + } + + /** + * Reads an instance of BlobStorageError from the JsonReader. + * + * @param jsonReader The JsonReader being read. + * @return An instance of BlobStorageError if the JsonReader was pointing to an instance of it, or null if it + * was pointing to JSON null. + * @throws IOException If an error occurs while reading the BlobStorageError. + */ + public static BlobStorageError fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject(reader -> { + // Buffer the next JSON object as ResponseError can take two forms: + // + // - A DataLakeStorageError object + // - A DataLakeStorageError object wrapped in an "error" node. + JsonReader bufferedReader = reader.bufferObject(); + bufferedReader.nextToken(); // Get to the START_OBJECT token. + while (bufferedReader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = bufferedReader.getFieldName(); + bufferedReader.nextToken(); + + if ("error".equals(fieldName)) { + // If the DataLakeStorageError was wrapped in the "error" node begin reading it now. + return readDataLakeError(bufferedReader); + } else { + bufferedReader.skipChildren(); + } + } + + // Otherwise reset the JsonReader and read the whole JSON object. + return readDataLakeError(bufferedReader.reset()); + }); + } + + private static BlobStorageError readDataLakeError(JsonReader jsonReader) throws IOException { + return jsonReader.readObject(reader -> { + BlobStorageError deserializedStorageError = new BlobStorageError(); + + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + + if ("code".equals(fieldName)) { + deserializedStorageError.errorCode = reader.getString(); + } else if ("message".equals(fieldName)) { + deserializedStorageError.message = reader.getString(); + } else if ("queryParameterName".equals(fieldName)) { + deserializedStorageError.queryParameterName = reader.getString(); + } else if ("queryParameterValue".equals(fieldName)) { + deserializedStorageError.queryParameterValue = reader.getString(); + } else if ("reason".equals(fieldName)) { + deserializedStorageError.reason = reader.getString(); + } else if ("extendedErrorDetail".equals(fieldName)) { + deserializedStorageError.extendedErrorDetail = reader.getString(); + } else { + reader.skipChildren(); + } + } + + return deserializedStorageError; + }); + } + + @Override + public XmlWriter toXml(XmlWriter xmlWriter, String rootElementName) throws XMLStreamException { + rootElementName = CoreUtils.isNullOrEmpty(rootElementName) ? "Error" : rootElementName; + xmlWriter.writeStartElement(rootElementName); + xmlWriter.writeStringElement("Code", errorCode); + xmlWriter.writeStringElement("Message", this.message); + xmlWriter.writeStringElement("QueryParameterName", this.queryParameterName); + xmlWriter.writeStringElement("QueryParameterValue", this.queryParameterValue); + xmlWriter.writeStringElement("Reason", this.reason); + xmlWriter.writeStringElement("ExtendedErrorDetail", this.extendedErrorDetail); + return xmlWriter.writeEndElement(); + } + + /** + * Reads an instance of BlobStorageError from the XmlReader. + * + * @param xmlReader The XmlReader being read. + * @return An instance of BlobStorageError if the XmlReader was pointing to an instance of it, or null if it was + * pointing to XML null. + * @throws XMLStreamException If an error occurs while reading the BlobStorageError. + */ + public static BlobStorageError fromXml(XmlReader xmlReader) throws XMLStreamException { + return fromXml(xmlReader, null); + } + + /** + * Reads an instance of BlobStorageError from the XmlReader. + * + * @param xmlReader The XmlReader being read. + * @param rootElementName Optional root element name to override the default defined by the model. Used to support + * cases where the model can deserialize from different root element names. + * @return An instance of BlobStorageError if the XmlReader was pointing to an instance of it, or null if it was + * pointing to XML null. + * @throws XMLStreamException If an error occurs while reading the BlobStorageError. + */ + public static BlobStorageError fromXml(XmlReader xmlReader, String rootElementName) throws XMLStreamException { + String finalRootElementName = CoreUtils.isNullOrEmpty(rootElementName) ? "Error" : rootElementName; + return xmlReader.readObject(finalRootElementName, reader -> { + BlobStorageError deserializedStorageError = new BlobStorageError(); + while (reader.nextElement() != XmlToken.END_ELEMENT) { + QName elementName = reader.getElementName(); + + if ("Code".equals(elementName.getLocalPart())) { + deserializedStorageError.errorCode = reader.getStringElement(); + } else if ("Message".equals(elementName.getLocalPart())) { + deserializedStorageError.message = reader.getStringElement(); + } else if ("QueryParameterName".equals(elementName.getLocalPart())) { + deserializedStorageError.queryParameterName = reader.getStringElement(); + } else if ("QueryParameterValue".equals(elementName.getLocalPart())) { + deserializedStorageError.queryParameterValue = reader.getStringElement(); + } else if ("Reason".equals(elementName.getLocalPart())) { + deserializedStorageError.reason = reader.getStringElement(); + } else if ("ExtendedErrorDetail".equals(elementName.getLocalPart())) { + deserializedStorageError.extendedErrorDetail = reader.getStringElement(); + } + } + + return deserializedStorageError; + }); + } +} diff --git a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/models/BlobStorageException.java b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/models/BlobStorageException.java index 733ed4be3138..35a70b5c11d0 100644 --- a/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/models/BlobStorageException.java +++ b/sdk/storage/azure-storage-blob/src/main/java/com/azure/storage/blob/models/BlobStorageException.java @@ -30,10 +30,19 @@ public final class BlobStorageException extends HttpResponseException { * @param response the HTTP response. * @param value the error code of the exception. */ - public BlobStorageException(String message, HttpResponse response, Object value) { + public BlobStorageException(String message, HttpResponse response, BlobStorageError value) { super(StorageImplUtils.convertStorageExceptionMessage(message, response), response, value); } + /** + * Gets the decoded error response returned by the service. + * + * @return The decoded error response returned by the service. + */ + public BlobStorageError getValue() { + return (BlobStorageError) super.getValue(); + } + /** * @return The error code returned by the service. */ diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/BlobErrorDeserializationTests.java b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/BlobErrorDeserializationTests.java new file mode 100644 index 000000000000..51e1856b24d3 --- /dev/null +++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/BlobErrorDeserializationTests.java @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.storage.blob; + +import com.azure.core.http.HttpHeaderName; +import com.azure.core.http.HttpHeaders; +import com.azure.core.http.HttpPipeline; +import com.azure.core.http.HttpPipelineBuilder; +import com.azure.core.test.http.MockHttpResponse; +import com.azure.core.test.utils.MockTokenCredential; +import com.azure.storage.blob.models.BlobStorageException; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests cases where the service returns a response that would result in a {@link BlobStorageException} being thrown + * with a response body that needs to be deserialized. + */ +public class BlobErrorDeserializationTests { + @Test + public void errorResponseBody() { + String errorResponse = "ContainerAlreadyExists" + + "The specified container already exists."; + HttpPipeline httpPipeline = new HttpPipelineBuilder() + .httpClient(request -> Mono.just(new MockHttpResponse(request, 409, + new HttpHeaders().set(HttpHeaderName.CONTENT_TYPE, "application/xml"), + errorResponse.getBytes(StandardCharsets.UTF_8)))) + .build(); + BlobContainerClient containerClient = new BlobContainerClientBuilder() + .endpoint("https://account.blob.core.windows.net/container") + .credential(new MockTokenCredential()) + .pipeline(httpPipeline) + .buildClient(); + + BlobStorageException exception = assertThrows(BlobStorageException.class, containerClient::create); + assertTrue(exception.getMessage().contains("The specified container already exists.")); + // assertEquals(BlobErrorCode.CONTAINER_ALREADY_EXISTS, exception.getErrorCode()); + } +} diff --git a/sdk/storage/azure-storage-file-datalake/src/main/java/com/azure/storage/file/datalake/implementation/util/DataLakeImplUtils.java b/sdk/storage/azure-storage-file-datalake/src/main/java/com/azure/storage/file/datalake/implementation/util/DataLakeImplUtils.java index ffe7a52e0e17..59769d22ee35 100644 --- a/sdk/storage/azure-storage-file-datalake/src/main/java/com/azure/storage/file/datalake/implementation/util/DataLakeImplUtils.java +++ b/sdk/storage/azure-storage-file-datalake/src/main/java/com/azure/storage/file/datalake/implementation/util/DataLakeImplUtils.java @@ -4,7 +4,9 @@ package com.azure.storage.file.datalake.implementation.util; import com.azure.core.util.logging.ClientLogger; +import com.azure.storage.blob.models.BlobStorageError; import com.azure.storage.blob.models.BlobStorageException; +import com.azure.storage.file.datalake.models.DataLakeStorageError; import com.azure.storage.file.datalake.models.DataLakeStorageException; import reactor.core.Exceptions; @@ -43,8 +45,15 @@ public static Throwable transformBlobStorageException(Throwable ex) { } private static DataLakeStorageException transformSingleBlobStorageException(BlobStorageException ex) { + BlobStorageError blobError = ex.getValue(); + DataLakeStorageError dataLakeError = null; + if (blobError != null) { + dataLakeError = new DataLakeStorageError().setErrorCode(blobError.getErrorCode()) + .setMessage(blobError.getMessage()); + } + return new DataLakeStorageException(ex.getServiceMessage(), ex.getResponse(), - ex.getValue()); + dataLakeError); } public static T returnOrConvertException(Supplier supplier, ClientLogger logger) { diff --git a/sdk/storage/azure-storage-file-datalake/src/main/java/com/azure/storage/file/datalake/models/DataLakeStorageError.java b/sdk/storage/azure-storage-file-datalake/src/main/java/com/azure/storage/file/datalake/models/DataLakeStorageError.java new file mode 100644 index 000000000000..0d85e33b5a2d --- /dev/null +++ b/sdk/storage/azure-storage-file-datalake/src/main/java/com/azure/storage/file/datalake/models/DataLakeStorageError.java @@ -0,0 +1,210 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.storage.file.datalake.models; + +import com.azure.core.util.CoreUtils; +import com.azure.json.JsonReader; +import com.azure.json.JsonSerializable; +import com.azure.json.JsonToken; +import com.azure.json.JsonWriter; +import com.azure.xml.XmlReader; +import com.azure.xml.XmlSerializable; +import com.azure.xml.XmlToken; +import com.azure.xml.XmlWriter; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import java.io.IOException; + +/** + * Represents an error response returned by the Azure Storage Blob service. + */ +public final class DataLakeStorageError implements JsonSerializable, XmlSerializable { + private String errorCode; + private String message; + private String queryParameterName; + private String queryParameterValue; + private String reason; + private String extendedErrorDetail; + + /** + * Creates an instance of DataLakeStorageError. + */ + public DataLakeStorageError() { + } + + /** + * Gets the error code returned by the Azure Storage DataLake service. + * + * @return The error code. + */ + public String getErrorCode() { + return errorCode; + } + + /** + * Sets the error code returned by the Azure Storage DataLake service. + * + * @param errorCode The error code. + * @return The updated DataLakeStorageError object. + */ + public DataLakeStorageError setErrorCode(String errorCode) { + this.errorCode = errorCode; + return this; + } + + /** + * Gets the error message returned by the Azure Storage DataLake service. + * + * @return The error message. + */ + public String getMessage() { + return message; + } + + /** + * Sets the error message returned by the Azure Storage DataLake service. + * + * @param message The error message. + * @return The updated DataLakeStorageError object. + */ + public DataLakeStorageError setMessage(String message) { + this.message = message; + return this; + } + + @Override + public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { + return jsonWriter.writeStartObject("Error") + .writeStringField("Code", errorCode) + .writeStringField("Message", this.message) + .writeStringField("QueryParameterName", this.queryParameterName) + .writeStringField("QueryParameterValue", this.queryParameterValue) + .writeStringField("Reason", this.reason) + .writeStringField("ExtendedErrorDetail", this.extendedErrorDetail) + .writeEndObject(); + } + + /** + * Reads an instance of DataLakeStorageError from the JsonReader. + * + * @param jsonReader The JsonReader being read. + * @return An instance of DataLakeStorageError if the JsonReader was pointing to an instance of it, or null if it + * was pointing to JSON null. + * @throws IOException If an error occurs while reading the DataLakeStorageError. + */ + public static DataLakeStorageError fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject(reader -> { + // Buffer the next JSON object as ResponseError can take two forms: + // + // - A DataLakeStorageError object + // - A DataLakeStorageError object wrapped in an "error" node. + JsonReader bufferedReader = reader.bufferObject(); + bufferedReader.nextToken(); // Get to the START_OBJECT token. + while (bufferedReader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = bufferedReader.getFieldName(); + bufferedReader.nextToken(); + + if ("error".equals(fieldName)) { + // If the DataLakeStorageError was wrapped in the "error" node begin reading it now. + return readDataLakeError(bufferedReader); + } else { + bufferedReader.skipChildren(); + } + } + + // Otherwise reset the JsonReader and read the whole JSON object. + return readDataLakeError(bufferedReader.reset()); + }); + } + + private static DataLakeStorageError readDataLakeError(JsonReader jsonReader) throws IOException { + return jsonReader.readObject(reader -> { + DataLakeStorageError deserializedStorageError = new DataLakeStorageError(); + + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + + if ("code".equals(fieldName)) { + deserializedStorageError.errorCode = reader.getString(); + } else if ("message".equals(fieldName)) { + deserializedStorageError.message = reader.getString(); + } else if ("queryParameterName".equals(fieldName)) { + deserializedStorageError.queryParameterName = reader.getString(); + } else if ("queryParameterValue".equals(fieldName)) { + deserializedStorageError.queryParameterValue = reader.getString(); + } else if ("reason".equals(fieldName)) { + deserializedStorageError.reason = reader.getString(); + } else if ("extendedErrorDetail".equals(fieldName)) { + deserializedStorageError.extendedErrorDetail = reader.getString(); + } else { + reader.skipChildren(); + } + } + + return deserializedStorageError; + }); + } + + @Override + public XmlWriter toXml(XmlWriter xmlWriter, String rootElementName) throws XMLStreamException { + rootElementName = CoreUtils.isNullOrEmpty(rootElementName) ? "Error" : rootElementName; + xmlWriter.writeStartElement(rootElementName); + xmlWriter.writeStringElement("Code", errorCode); + xmlWriter.writeStringElement("Message", this.message); + xmlWriter.writeStringElement("QueryParameterName", this.queryParameterName); + xmlWriter.writeStringElement("QueryParameterValue", this.queryParameterValue); + xmlWriter.writeStringElement("Reason", this.reason); + xmlWriter.writeStringElement("ExtendedErrorDetail", this.extendedErrorDetail); + return xmlWriter.writeEndElement(); + } + + /** + * Reads an instance of DataLakeStorageError from the XmlReader. + * + * @param xmlReader The XmlReader being read. + * @return An instance of DataLakeStorageError if the XmlReader was pointing to an instance of it, or null if it was + * pointing to XML null. + * @throws XMLStreamException If an error occurs while reading the DataLakeStorageError. + */ + public static DataLakeStorageError fromXml(XmlReader xmlReader) throws XMLStreamException { + return fromXml(xmlReader, null); + } + + /** + * Reads an instance of DataLakeStorageError from the XmlReader. + * + * @param xmlReader The XmlReader being read. + * @param rootElementName Optional root element name to override the default defined by the model. Used to support + * cases where the model can deserialize from different root element names. + * @return An instance of DataLakeStorageError if the XmlReader was pointing to an instance of it, or null if it was + * pointing to XML null. + * @throws XMLStreamException If an error occurs while reading the DataLakeStorageError. + */ + public static DataLakeStorageError fromXml(XmlReader xmlReader, String rootElementName) throws XMLStreamException { + String finalRootElementName = CoreUtils.isNullOrEmpty(rootElementName) ? "Error" : rootElementName; + return xmlReader.readObject(finalRootElementName, reader -> { + DataLakeStorageError deserializedStorageError = new DataLakeStorageError(); + while (reader.nextElement() != XmlToken.END_ELEMENT) { + QName elementName = reader.getElementName(); + + if ("Code".equals(elementName.getLocalPart())) { + deserializedStorageError.errorCode = reader.getStringElement(); + } else if ("Message".equals(elementName.getLocalPart())) { + deserializedStorageError.message = reader.getStringElement(); + } else if ("QueryParameterName".equals(elementName.getLocalPart())) { + deserializedStorageError.queryParameterName = reader.getStringElement(); + } else if ("QueryParameterValue".equals(elementName.getLocalPart())) { + deserializedStorageError.queryParameterValue = reader.getStringElement(); + } else if ("Reason".equals(elementName.getLocalPart())) { + deserializedStorageError.reason = reader.getStringElement(); + } else if ("ExtendedErrorDetail".equals(elementName.getLocalPart())) { + deserializedStorageError.extendedErrorDetail = reader.getStringElement(); + } + } + + return deserializedStorageError; + }); + } +} diff --git a/sdk/storage/azure-storage-file-datalake/src/main/java/com/azure/storage/file/datalake/models/DataLakeStorageException.java b/sdk/storage/azure-storage-file-datalake/src/main/java/com/azure/storage/file/datalake/models/DataLakeStorageException.java index b2b629ca006d..815558d7b6da 100644 --- a/sdk/storage/azure-storage-file-datalake/src/main/java/com/azure/storage/file/datalake/models/DataLakeStorageException.java +++ b/sdk/storage/azure-storage-file-datalake/src/main/java/com/azure/storage/file/datalake/models/DataLakeStorageException.java @@ -30,10 +30,19 @@ public final class DataLakeStorageException extends HttpResponseException { * @param response the HTTP response. * @param value the error code of the exception. */ - public DataLakeStorageException(String message, HttpResponse response, Object value) { + public DataLakeStorageException(String message, HttpResponse response, DataLakeStorageError value) { super(StorageImplUtils.convertStorageExceptionMessage(message, response), response, value); } + /** + * Gets the decoded error response returned by the service. + * + * @return The decoded error response returned by the service. + */ + public DataLakeStorageError getValue() { + return (DataLakeStorageError) super.getValue(); + } + /** * @return The error code returned by the service. */ diff --git a/sdk/storage/azure-storage-file-datalake/src/test/java/com/azure/storage/file/datalake/DataLakeErrorDeserializationTests.java b/sdk/storage/azure-storage-file-datalake/src/test/java/com/azure/storage/file/datalake/DataLakeErrorDeserializationTests.java new file mode 100644 index 000000000000..ee008b67a772 --- /dev/null +++ b/sdk/storage/azure-storage-file-datalake/src/test/java/com/azure/storage/file/datalake/DataLakeErrorDeserializationTests.java @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.storage.file.datalake; + +import com.azure.core.http.HttpHeaderName; +import com.azure.core.http.HttpHeaders; +import com.azure.core.http.HttpPipeline; +import com.azure.core.http.HttpPipelineBuilder; +import com.azure.core.test.http.MockHttpResponse; +import com.azure.core.test.utils.MockTokenCredential; +import com.azure.storage.file.datalake.models.DataLakeStorageException; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests cases where the service returns a response that would result in a {@link DataLakeStorageException} being thrown + * with a response body that needs to be deserialized. + */ +public class DataLakeErrorDeserializationTests { + @Test + public void errorResponseBody() { + String errorResponse = "ContainerAlreadyExists" + + "The specified container already exists."; + HttpPipeline httpPipeline = new HttpPipelineBuilder().httpClient(request -> Mono.just( + new MockHttpResponse(request, 409, new HttpHeaders().set(HttpHeaderName.CONTENT_TYPE, "application/xml"), + errorResponse.getBytes(StandardCharsets.UTF_8)))).build(); + DataLakeFileClient fileClient = new DataLakePathClientBuilder().endpoint( + "https://account.blob.core.windows.net/container") + .fileSystemName("filesystem") + .pathName("path") + .credential(new MockTokenCredential()) + .pipeline(httpPipeline) + .buildFileClient(); + + DataLakeStorageException exception = assertThrows(DataLakeStorageException.class, fileClient::create); + assertTrue(exception.getMessage().contains("The specified container already exists.")); + // assertEquals(BlobErrorCode.CONTAINER_ALREADY_EXISTS, exception.getErrorCode()); + } +} diff --git a/sdk/storage/azure-storage-file-share/src/main/java/com/azure/storage/file/share/ShareDirectoryAsyncClient.java b/sdk/storage/azure-storage-file-share/src/main/java/com/azure/storage/file/share/ShareDirectoryAsyncClient.java index 95bd5c4d05a5..a7a8c219601e 100644 --- a/sdk/storage/azure-storage-file-share/src/main/java/com/azure/storage/file/share/ShareDirectoryAsyncClient.java +++ b/sdk/storage/azure-storage-file-share/src/main/java/com/azure/storage/file/share/ShareDirectoryAsyncClient.java @@ -336,8 +336,7 @@ public Mono> createWithResponse(FileSmbProperties s * shareDirectoryAsyncClient.createWithResponse(options) * .subscribe(response -> * System.out.println("Completed creating the directory with status code:" + response.getStatusCode()), - * error -> System.err.print(error.toString()) - * ); + * error -> System.err.print(error.toString())); * * * diff --git a/sdk/storage/azure-storage-file-share/src/samples/java/com/azure/storage/file/share/ShareDirectoryAsyncJavaDocCodeSamples.java b/sdk/storage/azure-storage-file-share/src/samples/java/com/azure/storage/file/share/ShareDirectoryAsyncJavaDocCodeSamples.java index be12b46b3b2a..12f04fcfb02f 100644 --- a/sdk/storage/azure-storage-file-share/src/samples/java/com/azure/storage/file/share/ShareDirectoryAsyncJavaDocCodeSamples.java +++ b/sdk/storage/azure-storage-file-share/src/samples/java/com/azure/storage/file/share/ShareDirectoryAsyncJavaDocCodeSamples.java @@ -157,8 +157,7 @@ public void createDirectoryWithResponseAsync() { shareDirectoryAsyncClient.createWithResponse(options) .subscribe(response -> System.out.println("Completed creating the directory with status code:" + response.getStatusCode()), - error -> System.err.print(error.toString()) - ); + error -> System.err.print(error.toString())); // END: com.azure.storage.file.share.ShareDirectoryAsyncClient.createWithResponse#ShareDirectoryCreateOptions } diff --git a/sdk/storage/azure-storage-file-share/src/samples/java/com/azure/storage/file/share/ShareFileJavaDocCodeSamples.java b/sdk/storage/azure-storage-file-share/src/samples/java/com/azure/storage/file/share/ShareFileJavaDocCodeSamples.java index 0639a11035f4..1fba4c544ed1 100644 --- a/sdk/storage/azure-storage-file-share/src/samples/java/com/azure/storage/file/share/ShareFileJavaDocCodeSamples.java +++ b/sdk/storage/azure-storage-file-share/src/samples/java/com/azure/storage/file/share/ShareFileJavaDocCodeSamples.java @@ -179,7 +179,7 @@ public void createWithResponse() { new Context(key1, value1)); System.out.printf("Creating the file completed with status code %d", response.getStatusCode()); // END: com.azure.storage.file.share.ShareFileClient.createWithResponse#long-ShareFileHttpHeaders-FileSmbProperties-String-Map-Duration-Context -} + } /** * Generates a code sample for using {@link ShareFileClient#createWithResponse(long, ShareFileHttpHeaders, FileSmbProperties,