Repository: Azure/azure-sdk-for-go
Pull request #16688 (2021-12-17 18:23:26)

Add trademark section to root readme
Add trademark section to root readme

msftbot[bot]: <!--[AutoMerge]-->
Hello @RickWinter!

Because this pull request has the `auto-merge` label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

##### p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (`@msftbot`) and give me an instruction to get started! Learn more [here](https://github.com/microsoft/fluentui/wiki/Advanced-auto-merge).

Patch:
diff --git a/README.md b/README.md
index fdc5be55d1..582abd9ef9 100644
--- a/README.md
+++ b/README.md
@@ -91,3 +91,6 @@ Slack](https://gophers.slack.com/). Sign up
 
 See [CONTRIBUTING.md](https://github.com/Azure/azure-sdk-for-go/blob/main/CONTRIBUTING.md).
 
+## Trademarks
+
+This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/legal/intellectualproperty/trademarks/usage/general). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies.
diff --git a/documentation/MIGRATION_GUIDE.md b/documentation/MIGRATION_GUIDE.md
index 1ca76be40a..0f905c6d69 100644
--- a/documentation/MIGRATION_GUIDE.md
+++ b/documentation/MIGRATION_GUIDE.md
@@ -1,6 +1,6 @@
 ## Guide for migrating to `sdk/resourcemanager/**/arm**` from `services/**/mgmt/**`
 
-This document is intended for users that are familiar with an older version of the Azure SDK For Go for management modules (`services/**/mgmt/**`) and wish to migrate their application to the next version of Azure resource management libraries (`sdk/resourcemanager/**/arm**`)
+This document is intended for users that are familiar with the previous version of the Azure SDK For Go for management modules (`services/**/mgmt/**`) and wish to migrate their application to the next version of Azure resource management libraries (`sdk/resourcemanager/**/arm**`)
 
 **For users new to the Azure SDK For Go for resource management modules, please see the [README for 'sdk/azcore`](https://github.com/Azure/azure-sdk-for-go/tree/main/sdk/azcore) and the README for every individual package.**
 
@@ -28,13 +28,13 @@ The latest Azure SDK For Go for management modules is using the [Go Modules](htt
 
 ### Authentication
 
-In old version (`services/**/mgmt/**`), `autorest.Authorizer` is used in authentication process.
+In the previous version (`services/**/mgmt/**`), `autorest.Authorizer` is used in authentication process.
 
-In new version (`sdk/resourcemanager/**/arm**`), in order to provide a unified authentication based on Azure Identity for all Azure Go SDKs, the authentication mechanism has been re-designed and improved to offer a simpler interface.
+In the latest version (`sdk/resourcemanager/**/arm**`), in order to provide a unified authentication based on Azure Identity for all Azure Go SDKs, the authentication mechanism has been re-designed and improved to offer a simpler interface.
 
 To the show the code snippets for the change:
 
-**In old version (`services/**/mgmt/**`)**
+**Previous version (`services/**/mgmt/**`)**
 
 ```go
 authorizer, err := adal.NewServicePrincipalToken(oAuthToken, "<ClientId>", "<ClientSecret>", endpoint)
@@ -42,23 +42,22 @@ client := resources.NewGroupsClient("<SubscriptionId>")
 client.Authorizer = authorizer
 ```
 
-**Equivalent in new version (`sdk/resourcemanager/**/arm**`)**
+**Latest version (`sdk/resourcemanager/**/arm**`)**
 
 ```go
 credential, err := azidentity.NewClientSecretCredential("<TenantId>", "<ClientId>", "<ClientSecret>", nil)
-connection := arm.NewDefaultConnection(credential, nil)
-client := armresources.NewResourceGroupsClient(connection, "<SubscriptionId>")
+client := armresources.NewResourceGroupsClient("<SubscriptionId>", credential, nil)
 ```
 
-For detailed information on the benefits of using the new authentication classes, please refer to [this page](https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/azidentity/README.md)
+For detailed information on the benefits of using the new authentication types, please refer to [this page](https://github.com/Azure/azure-sdk-for-go/blob/main/sdk/azidentity/README.md)
 
 ### Error Handling
 
 There are some minor changes in the error handling.
 
-- When there is an error in the SDK request, in the old version (`services/**/mgmt/**`), the return value will all be non-nil, and you can get the raw HTTP response from the response value. In the new version (`sdk/resourcemanager/**/arm**`), the first return value will be empty and you need to cast the error to `HTTPResponse` interface to get the raw HTTP response. When the request is successful and there is no error returned, you will need to get the raw HTTP response in `RawResponse` property of the first return value.
+- When there is an error in the SDK request, in the previous version (`services/**/mgmt/**`), the return value will all be non-nil, and you can get the raw HTTP response from the response value. In the latest version (`sdk/resourcemanager/**/arm**`), the first return value will be empty and you need to convert the error to the `azcore.HTTPResponse` interface to get the raw HTTP response. When the request is successful and there is no error returned, you can get the raw HTTP response in `RawResponse` property of the first return value.
 
-**In old version (`services/**/mgmt/**`)**
+**Previous version (`services/**/mgmt/**`)**
 
 ```go
 resp, err := resourceGroupsClient.CreateOrUpdate(context.Background(), resourceGroupName, resourceGroupParameters)
@@ -67,7 +66,7 @@ if err != nil {
 }
 ```
 
-**Equivalent in new version (`sdk/resourcemanager/**/arm**`)**
+**Latest version (`sdk/resourcemanager/**/arm**`)**
 
 ```go
 resp, err := resourceGroupsClient.CreateOrUpdate(context.Background(), resourceGroupName, resourceGroupParameters, nil)
@@ -80,7 +79,7 @@ if err != nil {
 }
 ```
 
-**When there is no error in new version (`sdk/resourcemanager/**/arm**`)**
+**When there is no error in latest version (`sdk/resourcemanager/**/arm**`)**
 
 ```go
 resp, err := resourceGroupsClient.CreateOrUpdate(context.Background(), resourceGroupName, resourceGroupParameters, nil)
@@ -96,11 +95,11 @@ log.Printf("Status code: %d", resp.RawResponse.StatusCode)
 
 ### Long Running Operations
 
-In old version, if a request is a long-running operation, a struct `**Future` will be returned, which is an extension of the interface `azure.FutureAPI`. You need to invoke the `future.WaitForCompletionRef` to wait until it finishes.
+In the previous version, if a request is a long-running operation, a struct `**Future` will be returned, which is an extension of the interface `azure.FutureAPI`. You need to invoke the `future.WaitForCompletionRef` to wait until it finishes.
 
-In the new version, if a request is a long-running operation, the function name will start with `Begin` to indicate this function will return a poller struct which envelopes the interface `**Poller`, which is an extension of the interface `azcore.Poller`.
+In the latest version, if a request is a long-running operation, the function name will start with `Begin` to indicate this function will return a poller type which contains the polling methods.
 
-**In old version (`services/**/mgmt/**`)**
+**Previous version (`services/**/mgmt/**`)**
 
 ```go
 future, err := virtualMachinesClient.CreateOrUpdate(context.Background(), "<resource group name>", "<virtual machine name>", param)
@@ -117,7 +116,7 @@ if err != nil {
 log.Printf("virtual machine ID: %v", *vm.ID)
 ```
 
-**Equivalent in new version (`sdk/resourcemanager/**/arm**`)**
+**Latest version (`sdk/resourcemanager/**/arm**`)**
 
 ```go
 poller, err := client.BeginCreateOrUpdate(context.Background(), "<resource group name>", "<virtual machine name>", param, nil)
@@ -133,11 +132,11 @@ log.Printf("virtual machine ID: %v", *resp.VirtualMachine.ID)
 
 ### Pagination
 
-In old version, if a request is a paginated operation, a struct `**ResultPage` will be returned, which is a struct with some paging methods but no interfaces are defined regarding that.
+In the previous version, if a request is a paginated operation, a struct `**ResultPage` will be returned, which is a struct with some paging methods but no interfaces are defined regarding that.
 
-In new version, if a request is a paginated operation, an interface `**Pager` will be returned, which is an extension of the interface `azcore.Pager`.
+In the latest version, if a request is a paginated operation, a struct `**Pager` will be returned that contains the paging methods.
 
-**In old version (`services/**/mgmt/**`)**
+**Previous version (`services/**/mgmt/**`)**
 
 ```go
 pager, err := resourceGroupsClient.List(context.Background(), "", nil)
@@ -154,47 +153,49 @@ for p.NotDone() {
 }
 ```
 
-**Equivalent in new version (`sdk/resourcemanager/**/arm**`)**
+**Latest version (`sdk/resourcemanager/**/arm**`)**
 
 ```go
 pager := resourceGroupsClient.List(nil)
 for pager.NextPage(context.Background()) {
-    if err := pager.Err(); err != nil {
-        log.Fatalf("failed to advance page: %v", err)
-    }
     for _, rg := range pager.PageResponse().ResourceGroupListResult.Value {
         log.Printf("resource group ID: %s\n", *rg.ID)
     }
 }
+if err := pager.Err(); err != nil {
+    log.Fatalf("failed to advance page: %v", err)
+}
 ```
 
 ### Customized Policy
 
 Because of adopting Azure Core which is a shared library across all Azure SDKs, there is also a minor change regarding how customized policy in configured.
 
-In old version (`services/**/mgmt/**`), we use the `(autorest.Client).Sender`, `(autorest.Client).RequestInspector` and `(autorest.Client).ResponseInspector` properties in `github.com/Azure/go-autorest/autorest` module to provide customized interceptor for the HTTP traffic.
+In the previous version (`services/**/mgmt/**`), we use the `(autorest.Client).Sender`, `(autorest.Client).RequestInspector` and `(autorest.Client).ResponseInspector` properties in `github.com/Azure/go-autorest/autorest` module to provide customized interceptor for the HTTP traffic.
 
-In new version (`sdk/resourcemanager/**/arm**`), we use `(arm.ConnectionOptions).PerCallPolicies` and `(arm.ConnectionOptions).PerRetryPolicies` in `github.com/Azure/azure-sdk-for-go/sdk/azcore/arm` package instead to inject customized policy to the pipeline.
+In latest version (`sdk/resourcemanager/**/arm**`), we use `arm.ClientOptions.PerCallPolicies` and `arm.ClientOptions.PerRetryPolicies` in `github.com/Azure/azure-sdk-for-go/sdk/azcore/arm` package instead to inject customized policy to the pipeline.
 
 ### Custom HTTP Client
 
-Similar to the customized policy, there are changes regarding how the custom HTTP client is configured as well. You can now use the `(arm.ConnectionOptions).HTTPClient` option in `github.com/Azure/azure-sdk-for-go/sdk/azcore/arm` package to use your own implementation of HTTP client and plug in what they need into the configuration.
+Similar to the customized policy, there are changes regarding how the custom HTTP client is configured as well. You can now use the `arm.ClientOptions.Transport` option in `github.com/Azure/azure-sdk-for-go/sdk/azcore/arm` package to use your own implementation of HTTP client and plug in what they need into the configuration.  The HTTP client must implement the `policy.Transporter` interface.
 
-**In old version (`services/**/mgmt/**`)**
+**Previous version (`services/**/mgmt/**`)**
 ```go
 httpClient := NewYourOwnHTTPClient{}
 client := resources.NewGroupsClient("<SubscriptionId>")
 client.Sender = &httpClient
 ```
 
-**In new version (`sdk/resourcemanager/**/arm**`)**
+**Latest version (`sdk/resourcemanager/**/arm**`)**
 
 ```go
 httpClient := NewYourOwnHTTPClient{}
-connection := arm.NewConnection(credential, &arm.ConnectionOptions{
-    HTTPClient: &httpClient,
-})
-client := armresources.NewResourceGroupsClient(connection, "<SubscriptionId>")
+options := &arm.ClientOptions{
+    ClientOptions: policy.ClientOptions{
+        Transport: &httpClient,
+    },
+}
+client := armresources.NewResourceGroupsClient("<SubscriptionId>", credential, options)
 ```
 
 ## Need help?
diff --git a/documentation/new-version-quickstart.md b/documentation/new-version-quickstart.md
index 07e697fc50..decc168a1d 100644
--- a/documentation/new-version-quickstart.md
+++ b/documentation/new-version-quickstart.md
@@ -104,31 +104,20 @@ cred, err := azidentity.NewDefaultAzureCredential(nil)
 For more details on how authentication works in `azidentity`, please see the documentation for `azidentity` at [pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azidentity).
 
 
-Connecting to Azure 
--------------------
-
-Once you have a credential, create a connection to the desired ARM endpoint.  The `github.com/Azure/azure-sdk-for-go/sdk/azcore/arm` package provides facilities for connecting with ARM endpoints including public and sovereign clouds as well as Azure Stack.
-
-```go
-con := arm.NewDefaultConnection(cred, nil)
-```
-
-For more information on ARM connections, please see the documentation for `azcore` at [pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azcore](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azcore).
-
 Creating a Resource Management Client
 -------------------------------------
 
-Once you have a connection to ARM, you will need to decide what service to use and create a client to connect to that service. In this section, we will use `Compute` as our target service. The Compute modules consist of one or more clients. A client groups a set of related APIs, providing access to its functionality within the specified subscription. You will need to create one or more clients to access the APIs you require using your `arm.Connection`.
+Once you have a credential, you will need to decide what service to use and create a client to connect to that service. In this section, we will use `Compute` as our target service. The Compute modules consist of one or more clients. A client groups a set of related APIs, providing access to its functionality within the specified subscription. You will need to create one or more clients to access the APIs you require using your `azcore.TokenCredential`.
 
 To show an example, we will create a client to manage Virtual Machines. The code to achieve this task would be:
 
 ```go
-client := armcompute.NewVirtualMachinesClient(con, "<subscription ID>")
+client := armcompute.NewVirtualMachinesClient("<subscription ID>", credential, nil)
 ```
 You can use the same pattern to connect with other Azure services that you are using. For example, in order to manage Virtual Network resources, you would install the Network package and create a `VirtualNetwork` Client:
 
 ```go
-client := armnetwork.NewVirtualNetworksClient(acon, "<subscription ID>")
+client := armnetwork.NewVirtualNetworksClient("<subscription ID>", credential, nil)
 ```
 
 Interacting with Azure Resources
@@ -161,10 +150,12 @@ import (
     "os"
     "time"
 
+    "github.com/Azure/azure-sdk-for-go/sdk/azcore"
     "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
+    "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
     "github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
-    "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
     "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
+    "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources"
 )
 ```
 
@@ -181,8 +172,8 @@ var (
 
 ***Write a function to create a resource group***
 ```go
-func createResourceGroup(ctx context.Context, connection *arm.Connection) (armresources.ResourceGroupResponse, error) {
-	rgClient := armresources.NewResourceGroupsClient(connection, subscriptionId)
+func createResourceGroup(ctx context.Context, credential azcore.TokenCredential) (armresources.ResourceGroupsCreateOrUpdateResponse, error) {
+	rgClient := armresources.NewResourceGroupsClient(subscriptionId, credential, nil)
 
     param := armresources.ResourceGroup{
         Location: to.StringPtr(location),
@@ -199,13 +190,8 @@ func main() {
     if err != nil {
         log.Fatalf("authentication failure: %+v", err)
     }
-    conn := arm.NewDefaultConnection(cred, &arm.ConnectionOptions{
-        Logging: azcore.LogOptions{
-            IncludeBody: true,
-        },
-    })
     
-    resourceGroup, err := createResourceGroup(ctx, conn)
+    resourceGroup, err := createResourceGroup(ctx, cred)
     if err != nil {
         log.Fatalf("cannot create resource group: %+v", err)
     }
@@ -221,9 +207,9 @@ Example: Managing Resource Groups
 ***Update a resource group***
 
 ```go
-func updateResourceGroup(ctx context.Context, connection *arm.Connection) (armresources.ResourceGroupResponse, error) {
-    rgClient := armresources.NewResourceGroupsClient(connection, subscriptionId)
-    
+func updateResourceGroup(ctx context.Context, credential azcore.TokenCredential) (armresources.ResourceGroupsUpdateResponse, error) {
+    rgClient := armresources.NewResourceGroupsClient(subscriptionId, credential, nil)
+
     update := armresources.ResourceGroupPatchable{
         Tags: map[string]*string{
             "new": to.StringPtr("tag"),
@@ -236,15 +222,15 @@ func updateResourceGroup(ctx context.Context, connection *arm.Connection) (armre
 ***List all resource groups***
 
 ```go
-func listResourceGroups(ctx context.Context, connection *arm.Connection) ([]*armresources.ResourceGroup, error) {
-    rgClient := armresources.NewResourceGroupsClient(connection, subscriptionId)
+func listResourceGroups(ctx context.Context, credential azcore.TokenCredential) ([]*armresources.ResourceGroup, error) {
+    rgClient := armresources.NewResourceGroupsClient(subscriptionId, credential, nil)
     
     pager := rgClient.List(nil)
     
     var resourceGroups []*armresources.ResourceGroup
     for pager.NextPage(ctx) {
         resp := pager.PageResponse()
-        if resp.ResourceGroupListResult != nil {
+        if resp.ResourceGroupListResult.Value != nil {
             resourceGroups = append(resourceGroups, resp.ResourceGroupListResult.Value...)
         }
     }
@@ -255,17 +241,15 @@ func listResourceGroups(ctx context.Context, connection *arm.Connection) ([]*arm
 ***Delete a resource group***
 
 ```go
-func deleteResourceGroup(ctx context.Context, connection *arm.Connection) error {
-    rgClient := armresources.NewResourceGroupsClient(connection, subscriptionId)
+func deleteResourceGroup(ctx context.Context, credential azcore.TokenCredential) error {
+    rgClient := armresources.NewResourceGroupsClient(subscriptionId, credential, nil)
     
     poller, err := rgClient.BeginDelete(ctx, resourceGroupName, nil)
     if err != nil {
         return err
     }
-    if _, err := poller.PollUntilDone(ctx, interval); err != nil {
-        return err
-    }
-    return nil
+    _, err = poller.PollUntilDone(ctx, interval)
+    return err
 }
 ```
 
@@ -276,32 +260,26 @@ func main() {
 	if err != nil {
 		log.Fatalf("authentication failure: %+v", err)
 	}
-	conn := arm.NewDefaultConnection(cred, &arm.ConnectionOptions{
-		Logging: azcore.LogOptions{
-			IncludeBody: true,
-		},
-	})
-
 
-    resourceGroup, err := createResourceGroup(ctx, conn)
+    resourceGroup, err := createResourceGroup(ctx, cred)
     if err != nil {
         log.Fatalf("cannot create resource group: %+v", err)
     }
     log.Printf("Resource Group %s created", *resourceGroup.ResourceGroup.ID)
 
-    updatedRG, err := updateResourceGroup(ctx, conn)
+    updatedRG, err := updateResourceGroup(ctx, cred)
     if err != nil {
         log.Fatalf("cannot update resource group: %+v", err)
     }
     log.Printf("Resource Group %s updated", *updatedRG.ResourceGroup.ID)
 
-    rgList, err := listResourceGroups(ctx, conn)
+    rgList, err := listResourceGroups(ctx, cred)
     if err != nil {
         log.Fatalf("cannot list resource group: %+v", err)
     }
     log.Printf("We totally have %d resource groups", len(rgList))
 
-    if err := deleteResourceGroup(ctx, conn); err != nil {
+    if err := deleteResourceGroup(ctx, cred); err != nil {
         log.Fatalf("cannot delete resource group: %+v", err)
     }
     log.Printf("Resource Group deleted")
diff --git a/sdk/azcore/CHANGELOG.md b/sdk/azcore/CHANGELOG.md
index bc8d662ea6..c216f4c5e6 100644
--- a/sdk/azcore/CHANGELOG.md
+++ b/sdk/azcore/CHANGELOG.md
@@ -4,11 +4,15 @@
 
 ### Features Added
 * Added `AllowedHeaders` and `AllowedQueryParams` to `policy.LogOptions` to control which headers and query parameters are written to the logger.
+* Added `azcore.ResponseError` type which is returned from APIs when a non-success HTTP status code is received.
 
 ### Breaking Changes
 * Moved `[]policy.Policy` parameters of `arm/runtime.NewPipeline` and `runtime.NewPipeline` into a new struct, `runtime.PipelineOptions`
 * Renamed `arm/ClientOptions.Host` to `.Endpoint`
 * Moved `Request.SkipBodyDownload` method to function `runtime.SkipBodyDownload`
+* Removed `azcore.HTTPResponse` interface type
+* `arm.NewPoller()` and `runtime.NewPoller()` no longer require an `eu` parameter
+* `runtime.NewResponseError()` no longer requires an `error` parameter
 
 ### Bugs Fixed
 
diff --git a/sdk/azcore/arm/runtime/policy_register_rp.go b/sdk/azcore/arm/runtime/policy_register_rp.go
index e4e1823fa7..708a60ff29 100644
--- a/sdk/azcore/arm/runtime/policy_register_rp.go
+++ b/sdk/azcore/arm/runtime/policy_register_rp.go
@@ -10,7 +10,6 @@ import (
 	"context"
 	"errors"
 	"fmt"
-	"io/ioutil"
 	"net/http"
 	"net/url"
 	"strings"
@@ -246,7 +245,7 @@ func (client *providersOperations) getCreateRequest(ctx context.Context, resourc
 // getHandleResponse handles the Get response.
 func (client *providersOperations) getHandleResponse(resp *http.Response) (*ProviderResponse, error) {
 	if !runtime.HasStatusCode(resp, http.StatusOK) {
-		return nil, client.getHandleError(resp)
+		return nil, shared.NewResponseError(resp)
 	}
 	result := ProviderResponse{RawResponse: resp}
 	err := runtime.UnmarshalAsJSON(resp, &result.Provider)
@@ -256,18 +255,6 @@ func (client *providersOperations) getHandleResponse(resp *http.Response) (*Prov
 	return &result, err
 }
 
-// getHandleError handles the Get error response.
-func (client *providersOperations) getHandleError(resp *http.Response) error {
-	body, err := ioutil.ReadAll(resp.Body)
-	if err != nil {
-		return shared.NewResponseError(err, resp)
-	}
-	if len(body) == 0 {
-		return shared.NewResponseError(errors.New(resp.Status), resp)
-	}
-	return shared.NewResponseError(errors.New(string(body)), resp)
-}
-
 // Register - Registers a subscription with a resource provider.
 func (client *providersOperations) Register(ctx context.Context, resourceProviderNamespace string) (*ProviderResponse, error) {
 	req, err := client.registerCreateRequest(ctx, resourceProviderNamespace)
@@ -303,7 +290,7 @@ func (client *providersOperations) registerCreateRequest(ctx context.Context, re
 // registerHandleResponse handles the Register response.
 func (client *providersOperations) registerHandleResponse(resp *http.Response) (*ProviderResponse, error) {
 	if !runtime.HasStatusCode(resp, http.StatusOK) {
-		return nil, client.registerHandleError(resp)
+		return nil, shared.NewResponseError(resp)
 	}
 	result := ProviderResponse{RawResponse: resp}
 	err := runtime.UnmarshalAsJSON(resp, &result.Provider)
@@ -313,18 +300,6 @@ func (client *providersOperations) registerHandleResponse(resp *http.Response) (
 	return &result, err
 }
 
-// registerHandleError handles the Register error response.
-func (client *providersOperations) registerHandleError(resp *http.Response) error {
-	body, err := ioutil.ReadAll(resp.Body)
-	if err != nil {
-		return shared.NewResponseError(err, resp)
-	}
-	if len(body) == 0 {
-		return shared.NewResponseError(errors.New(resp.Status), resp)
-	}
-	return shared.NewResponseError(errors.New(string(body)), resp)
-}
-
 // ProviderResponse is the response envelope for operations that return a Provider type.
 type ProviderResponse struct {
 	// Resource provider information.
diff --git a/sdk/azcore/arm/runtime/poller.go b/sdk/azcore/arm/runtime/poller.go
index e149b2542d..f1afe3473f 100644
--- a/sdk/azcore/arm/runtime/poller.go
+++ b/sdk/azcore/arm/runtime/poller.go
@@ -22,7 +22,7 @@ import (
 
 // NewPoller creates a Poller based on the provided initial response.
 // pollerID - a unique identifier for an LRO.  it's usually the client.Method string.
-func NewPoller(pollerID string, finalState string, resp *http.Response, pl pipeline.Pipeline, eu func(*http.Response) error) (*pollers.Poller, error) {
+func NewPoller(pollerID string, finalState string, resp *http.Response, pl pipeline.Pipeline) (*pollers.Poller, error) {
 	defer resp.Body.Close()
 	// this is a back-stop in case the swagger is incorrect (i.e. missing one or more status codes for success).
 	// ideally the codegen should return an error if the initial response failed and not even create a poller.
@@ -50,12 +50,12 @@ func NewPoller(pollerID string, finalState string, resp *http.Response, pl pipel
 	if err != nil {
 		return nil, err
 	}
-	return pollers.NewPoller(lro, resp, pl, eu), nil
+	return pollers.NewPoller(lro, resp, pl), nil
 }
 
 // NewPollerFromResumeToken creates a Poller from a resume token string.
 // pollerID - a unique identifier for an LRO.  it's usually the client.Method string.
-func NewPollerFromResumeToken(pollerID string, token string, pl pipeline.Pipeline, eu func(*http.Response) error) (*pollers.Poller, error) {
+func NewPollerFromResumeToken(pollerID string, token string, pl pipeline.Pipeline) (*pollers.Poller, error) {
 	kind, err := pollers.KindFromToken(pollerID, token)
 	if err != nil {
 		return nil, err
@@ -78,5 +78,5 @@ func NewPollerFromResumeToken(pollerID string, token string, pl pipeline.Pipelin
 	if err = json.Unmarshal([]byte(token), lro); err != nil {
 		return nil, err
 	}
-	return pollers.NewPoller(lro, nil, pl, eu), nil
+	return pollers.NewPoller(lro, nil, pl), nil
 }
diff --git a/sdk/azcore/errors.go b/sdk/azcore/errors.go
index 222c8f85f2..daf415f1bf 100644
--- a/sdk/azcore/errors.go
+++ b/sdk/azcore/errors.go
@@ -7,20 +7,10 @@
 package azcore
 
 import (
-	"net/http"
-
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
-	"github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo"
 )
 
-// HTTPResponse provides access to an HTTP response when available.
-// Errors returned from failed API calls will implement this interface.
-// Use errors.As() to access this interface in the error chain.
-// If there was no HTTP response then this interface will be omitted
-// from any error in the chain.
-type HTTPResponse interface {
-	RawResponse() *http.Response
-}
-
-var _ HTTPResponse = (*shared.ResponseError)(nil)
-var _ errorinfo.NonRetriable = (*shared.ResponseError)(nil)
+// ResponseError is returned when a request is made to a service and
+// the service returns a non-success HTTP status code.
+// Use errors.As() to access this type in the error chain.
+type ResponseError = shared.ResponseError
diff --git a/sdk/azcore/internal/pollers/poller.go b/sdk/azcore/internal/pollers/poller.go
index 2a4bfaecab..e8da46bcfe 100644
--- a/sdk/azcore/internal/pollers/poller.go
+++ b/sdk/azcore/internal/pollers/poller.go
@@ -55,15 +55,14 @@ func PollerType(p *Poller) reflect.Type {
 }
 
 // NewPoller creates a Poller from the specified input.
-func NewPoller(lro Operation, resp *http.Response, pl pipeline.Pipeline, eu func(*http.Response) error) *Poller {
-	return &Poller{lro: lro, pl: pl, eu: eu, resp: resp}
+func NewPoller(lro Operation, resp *http.Response, pl pipeline.Pipeline) *Poller {
+	return &Poller{lro: lro, pl: pl, resp: resp}
 }
 
 // Poller encapsulates state and logic for polling on long-running operations.
 type Poller struct {
 	lro  Operation
 	pl   pipeline.Pipeline
-	eu   func(*http.Response) error
 	resp *http.Response
 	err  error
 }
@@ -97,7 +96,7 @@ func (l *Poller) Poll(ctx context.Context) (*http.Response, error) {
 	defer resp.Body.Close()
 	if !StatusCodeValid(resp) {
 		// the LRO failed.  unmarshall the error and update state
-		l.err = l.eu(resp)
+		l.err = shared.NewResponseError(resp)
 		l.resp = nil
 		return nil, l.err
 	}
@@ -107,7 +106,7 @@ func (l *Poller) Poll(ctx context.Context) (*http.Response, error) {
 	l.resp = resp
 	log.Writef(log.EventLRO, "Status %s", l.lro.Status())
 	if Failed(l.lro.Status()) {
-		l.err = l.eu(resp)
+		l.err = shared.NewResponseError(resp)
 		l.resp = nil
 		return nil, l.err
 	}
@@ -144,7 +143,7 @@ func (l *Poller) FinalResponse(ctx context.Context, respType interface{}) (*http
 			return nil, err
 		}
 		if !StatusCodeValid(resp) {
-			return nil, l.eu(resp)
+			return nil, shared.NewResponseError(resp)
 		}
 		l.resp = resp
 	}
diff --git a/sdk/azcore/internal/shared/response_error.go b/sdk/azcore/internal/shared/response_error.go
new file mode 100644
index 0000000000..e2297706d7
--- /dev/null
+++ b/sdk/azcore/internal/shared/response_error.go
@@ -0,0 +1,133 @@
+//go:build go1.16
+// +build go1.16
+
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package shared
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"regexp"
+)
+
+// NewResponseError creates a new *ResponseError from the provided HTTP response.
+func NewResponseError(resp *http.Response) error {
+	respErr := &ResponseError{
+		StatusCode:  resp.StatusCode,
+		RawResponse: resp,
+	}
+
+	// prefer the error code in the response header
+	if ec := resp.Header.Get("x-ms-error-code"); ec != "" {
+		respErr.ErrorCode = ec
+		return respErr
+	}
+
+	// if we didn't get x-ms-error-code, check in the response body
+	body, err := Payload(resp)
+	if err != nil {
+		return err
+	}
+
+	if len(body) > 0 {
+		if code := extractErrorCodeJSON(body); code != "" {
+			respErr.ErrorCode = code
+		} else if code := extractErrorCodeXML(body); code != "" {
+			respErr.ErrorCode = code
+		}
+	}
+
+	return respErr
+}
+
+func extractErrorCodeJSON(body []byte) string {
+	var rawObj map[string]interface{}
+	if err := json.Unmarshal(body, &rawObj); err != nil {
+		// not a JSON object
+		return ""
+	}
+
+	// check if this is a wrapped error, i.e. { "error": { ... } }
+	// if so then unwrap it
+	if wrapped, ok := rawObj["error"]; ok {
+		unwrapped, ok := wrapped.(map[string]interface{})
+		if !ok {
+			return ""
+		}
+		rawObj = unwrapped
+	}
+
+	// now check for the error code
+	code, ok := rawObj["code"]
+	if !ok {
+		return ""
+	}
+	codeStr, ok := code.(string)
+	if !ok {
+		return ""
+	}
+	return codeStr
+}
+
+func extractErrorCodeXML(body []byte) string {
+	// regular expression is much easier than dealing with the XML parser
+	rx := regexp.MustCompile(`<[c|C]ode>\s*(\w+)\s*<\/[c|C]ode>`)
+	res := rx.FindStringSubmatch(string(body))
+	if len(res) != 2 {
+		return ""
+	}
+	// first submatch is the entire thing, second one is the captured error code
+	return res[1]
+}
+
+// ResponseError is returned when a request is made to a service and
+// the service returns a non-success HTTP status code.
+// Use errors.As() to access this type in the error chain.
+type ResponseError struct {
+	// ErrorCode is the error code returned by the resource provider if available.
+	ErrorCode string
+
+	// StatusCode is the HTTP status code as defined in https://pkg.go.dev/net/http#pkg-constants.
+	StatusCode int
+
+	// RawResponse is the underlying HTTP response.
+	RawResponse *http.Response
+}
+
+// Error implements the error interface for type ResponseError.
+// Note that the message contents are not contractual and can change over time.
+func (e *ResponseError) Error() string {
+	// write the request method and URL with response status code
+	msg := &bytes.Buffer{}
+	fmt.Fprintf(msg, "%s %s://%s%s\n", e.RawResponse.Request.Method, e.RawResponse.Request.URL.Scheme, e.RawResponse.Request.URL.Host, e.RawResponse.Request.URL.Path)
+	fmt.Fprintln(msg, "--------------------------------------------------------------------------------")
+	fmt.Fprintf(msg, "RESPONSE %d: %s\n", e.RawResponse.StatusCode, e.RawResponse.Status)
+	if e.ErrorCode != "" {
+		fmt.Fprintf(msg, "ERROR CODE: %s\n", e.ErrorCode)
+	} else {
+		fmt.Fprintln(msg, "ERROR CODE UNAVAILABLE")
+	}
+	fmt.Fprintln(msg, "--------------------------------------------------------------------------------")
+	body, err := Payload(e.RawResponse)
+	if err != nil {
+		// this really shouldn't fail at this point as the response
+		// body is already cached (it was read in NewResponseError)
+		fmt.Fprintf(msg, "Error reading response body: %v", err)
+	} else if len(body) > 0 {
+		if err := json.Indent(msg, body, "", "  "); err != nil {
+			// failed to pretty-print so just dump it verbatim
+			fmt.Fprint(msg, string(body))
+		}
+		// the standard library doesn't have a pretty-printer for XML
+		fmt.Fprintln(msg)
+	} else {
+		fmt.Fprintln(msg, "Response contained no body")
+	}
+	fmt.Fprintln(msg, "--------------------------------------------------------------------------------")
+
+	return msg.String()
+}
diff --git a/sdk/azcore/internal/shared/shared.go b/sdk/azcore/internal/shared/shared.go
index 7b1f348224..5e008b18e5 100644
--- a/sdk/azcore/internal/shared/shared.go
+++ b/sdk/azcore/internal/shared/shared.go
@@ -37,35 +37,6 @@ func NopCloser(rs io.ReadSeeker) io.ReadSeekCloser {
 	return nopCloser{rs}
 }
 
-func NewResponseError(inner error, resp *http.Response) error {
-	return &ResponseError{inner: inner, resp: resp}
-}
-
-type ResponseError struct {
-	inner error
-	resp  *http.Response
-}
-
-// Error implements the error interface for type ResponseError.
-func (e *ResponseError) Error() string {
-	return e.inner.Error()
-}
-
-// Unwrap returns the inner error.
-func (e *ResponseError) Unwrap() error {
-	return e.inner
-}
-
-// RawResponse returns the HTTP response associated with this error.
-func (e *ResponseError) RawResponse() *http.Response {
-	return e.resp
-}
-
-// NonRetriable indicates this error is non-transient.
-func (e *ResponseError) NonRetriable() {
-	// marker method
-}
-
 // Delay waits for the duration to elapse or the context to be cancelled.
 func Delay(ctx context.Context, delay time.Duration) error {
 	select {
@@ -132,6 +103,81 @@ func HasStatusCode(resp *http.Response, statusCodes ...int) bool {
 	return false
 }
 
+// Payload reads and returns the response body or an error.
+// On a successful read, the response body is cached.
+// Subsequent reads will access the cached value.
+func Payload(resp *http.Response) ([]byte, error) {
+	// r.Body won't be a nopClosingBytesReader if downloading was skipped
+	if buf, ok := resp.Body.(*NopClosingBytesReader); ok {
+		return buf.Bytes(), nil
+	}
+	bytesBody, err := ioutil.ReadAll(resp.Body)
+	resp.Body.Close()
+	if err != nil {
+		return nil, err
+	}
+	resp.Body = &NopClosingBytesReader{s: bytesBody, i: 0}
+	return bytesBody, nil
+}
+
+// NopClosingBytesReader is an io.ReadSeekCloser around a byte slice.
+// It also provides direct access to the byte slice to avoid rereading.
+type NopClosingBytesReader struct {
+	s []byte
+	i int64
+}
+
+// NewNopClosingBytesReader creates a new NopClosingBytesReader around the specified byte slice.
+func NewNopClosingBytesReader(data []byte) *NopClosingBytesReader {
+	return &NopClosingBytesReader{s: data}
+}
+
+// Bytes returns the underlying byte slice.
+func (r *NopClosingBytesReader) Bytes() []byte {
+	return r.s
+}
+
+// Close implements the io.Closer interface.
+func (*NopClosingBytesReader) Close() error {
+	return nil
+}
+
+// Read implements the io.Reader interface.
+func (r *NopClosingBytesReader) Read(b []byte) (n int, err error) {
+	if r.i >= int64(len(r.s)) {
+		return 0, io.EOF
+	}
+	n = copy(b, r.s[r.i:])
+	r.i += int64(n)
+	return
+}
+
+// Set replaces the existing byte slice with the specified byte slice and resets the reader.
+func (r *NopClosingBytesReader) Set(b []byte) {
+	r.s = b
+	r.i = 0
+}
+
+// Seek implements the io.Seeker interface.
+func (r *NopClosingBytesReader) Seek(offset int64, whence int) (int64, error) {
+	var i int64
+	switch whence {
+	case io.SeekStart:
+		i = offset
+	case io.SeekCurrent:
+		i = r.i + offset
+	case io.SeekEnd:
+		i = int64(len(r.s)) + offset
+	default:
+		return 0, errors.New("nopClosingBytesReader: invalid whence")
+	}
+	if i < 0 {
+		return 0, errors.New("nopClosingBytesReader: negative position")
+	}
+	r.i = i
+	return i, nil
+}
+
 const defaultScope = "/.default"
 
 // EndpointToScope converts the provided URL endpoint to its default scope.
diff --git a/sdk/azcore/runtime/errors.go b/sdk/azcore/runtime/errors.go
index badf62a3bd..0b19b5c74d 100644
--- a/sdk/azcore/runtime/errors.go
+++ b/sdk/azcore/runtime/errors.go
@@ -12,10 +12,8 @@ import (
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
 )
 
-// NewResponseError wraps the specified error with an error that provides access to an HTTP response.
-// If an HTTP request returns a non-successful status code, wrap the response and the associated error
-// in this error type so that callers can access the underlying *http.Response as required.
-// DO NOT wrap failed HTTP requests that returned an error and no response with this type.
-func NewResponseError(inner error, resp *http.Response) error {
-	return shared.NewResponseError(inner, resp)
+// NewResponseError creates an *azcore.ResponseError from the provided HTTP response.
+// Call this when a service request returns a non-successful status code.
+func NewResponseError(resp *http.Response) error {
+	return shared.NewResponseError(resp)
 }
diff --git a/sdk/azcore/runtime/policy_body_download.go b/sdk/azcore/runtime/policy_body_download.go
index cc294fcd8e..42a0279c00 100644
--- a/sdk/azcore/runtime/policy_body_download.go
+++ b/sdk/azcore/runtime/policy_body_download.go
@@ -7,13 +7,12 @@
 package runtime
 
 import (
-	"errors"
 	"fmt"
-	"io"
 	"io/ioutil"
 	"net/http"
 	"strings"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
 	"github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo"
 )
@@ -36,7 +35,7 @@ func bodyDownloadPolicy(req *policy.Request) (*http.Response, error) {
 	if err != nil {
 		return resp, newBodyDownloadError(err, req)
 	}
-	resp.Body = &nopClosingBytesReader{s: b}
+	resp.Body = shared.NewNopClosingBytesReader(b)
 	return resp, err
 }
 
@@ -75,56 +74,3 @@ func (b *bodyDownloadError) Unwrap() error {
 }
 
 var _ errorinfo.NonRetriable = (*bodyDownloadError)(nil)
-
-// nopClosingBytesReader is an io.ReadSeekCloser around a byte slice.
-// It also provides direct access to the byte slice.
-type nopClosingBytesReader struct {
-	s []byte
-	i int64
-}
-
-// Bytes returns the underlying byte slice.
-func (r *nopClosingBytesReader) Bytes() []byte {
-	return r.s
-}
-
-// Close implements the io.Closer interface.
-func (*nopClosingBytesReader) Close() error {
-	return nil
-}
-
-// Read implements the io.Reader interface.
-func (r *nopClosingBytesReader) Read(b []byte) (n int, err error) {
-	if r.i >= int64(len(r.s)) {
-		return 0, io.EOF
-	}
-	n = copy(b, r.s[r.i:])
-	r.i += int64(n)
-	return
-}
-
-// Set replaces the existing byte slice with the specified byte slice and resets the reader.
-func (r *nopClosingBytesReader) Set(b []byte) {
-	r.s = b
-	r.i = 0
-}
-
-// Seek implements the io.Seeker interface.
-func (r *nopClosingBytesReader) Seek(offset int64, whence int) (int64, error) {
-	var i int64
-	switch whence {
-	case io.SeekStart:
-		i = offset
-	case io.SeekCurrent:
-		i = r.i + offset
-	case io.SeekEnd:
-		i = int64(len(r.s)) + offset
-	default:
-		return 0, errors.New("nopClosingBytesReader: invalid whence")
-	}
-	if i < 0 {
-		return 0, errors.New("nopClosingBytesReader: negative position")
-	}
-	r.i = i
-	return i, nil
-}
diff --git a/sdk/azcore/runtime/poller.go b/sdk/azcore/runtime/poller.go
index 783debd253..fbb364cdf9 100644
--- a/sdk/azcore/runtime/poller.go
+++ b/sdk/azcore/runtime/poller.go
@@ -21,7 +21,7 @@ import (
 
 // NewPoller creates a Poller based on the provided initial response.
 // pollerID - a unique identifier for an LRO, it's usually the client.Method string.
-func NewPoller(pollerID string, resp *http.Response, pl pipeline.Pipeline, eu func(*http.Response) error) (*pollers.Poller, error) {
+func NewPoller(pollerID string, resp *http.Response, pl pipeline.Pipeline) (*pollers.Poller, error) {
 	defer resp.Body.Close()
 	// this is a back-stop in case the swagger is incorrect (i.e. missing one or more status codes for success).
 	// ideally the codegen should return an error if the initial response failed and not even create a poller.
@@ -42,12 +42,12 @@ func NewPoller(pollerID string, resp *http.Response, pl pipeline.Pipeline, eu fu
 	if err != nil {
 		return nil, err
 	}
-	return pollers.NewPoller(lro, resp, pl, eu), nil
+	return pollers.NewPoller(lro, resp, pl), nil
 }
 
 // NewPollerFromResumeToken creates a Poller from a resume token string.
 // pollerID - a unique identifier for an LRO, it's usually the client.Method string.
-func NewPollerFromResumeToken(pollerID string, token string, pl pipeline.Pipeline, eu func(*http.Response) error) (*pollers.Poller, error) {
+func NewPollerFromResumeToken(pollerID string, token string, pl pipeline.Pipeline) (*pollers.Poller, error) {
 	kind, err := pollers.KindFromToken(pollerID, token)
 	if err != nil {
 		return nil, err
@@ -67,5 +67,5 @@ func NewPollerFromResumeToken(pollerID string, token string, pl pipeline.Pipelin
 	if err = json.Unmarshal([]byte(token), lro); err != nil {
 		return nil, err
 	}
-	return pollers.NewPoller(lro, nil, pl, eu), nil
+	return pollers.NewPoller(lro, nil, pl), nil
 }
diff --git a/sdk/azcore/runtime/response.go b/sdk/azcore/runtime/response.go
index 4886c310d4..8df3ca8131 100644
--- a/sdk/azcore/runtime/response.go
+++ b/sdk/azcore/runtime/response.go
@@ -21,18 +21,9 @@ import (
 
 // Payload reads and returns the response body or an error.
 // On a successful read, the response body is cached.
+// Subsequent reads will access the cached value.
 func Payload(resp *http.Response) ([]byte, error) {
-	// r.Body won't be a nopClosingBytesReader if downloading was skipped
-	if buf, ok := resp.Body.(*nopClosingBytesReader); ok {
-		return buf.Bytes(), nil
-	}
-	bytesBody, err := ioutil.ReadAll(resp.Body)
-	resp.Body.Close()
-	if err != nil {
-		return nil, err
-	}
-	resp.Body = &nopClosingBytesReader{s: bytesBody, i: 0}
-	return bytesBody, nil
+	return shared.Payload(resp)
 }
 
 // HasStatusCode returns true if the Response's status code is one of the specified values.
@@ -108,7 +99,7 @@ func removeBOM(resp *http.Response) error {
 	// UTF8
 	trimmed := bytes.TrimPrefix(payload, []byte("\xef\xbb\xbf"))
 	if len(trimmed) < len(payload) {
-		resp.Body.(*nopClosingBytesReader).Set(trimmed)
+		resp.Body.(*shared.NopClosingBytesReader).Set(trimmed)
 	}
 	return nil
 }
diff --git a/sdk/azidentity/errors.go b/sdk/azidentity/errors.go
index df38f2957a..777d0d663b 100644
--- a/sdk/azidentity/errors.go
+++ b/sdk/azidentity/errors.go
@@ -7,15 +7,14 @@ import (
 	"errors"
 	"net/http"
 
-	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
 	"github.com/Azure/azure-sdk-for-go/sdk/internal/errorinfo"
 	msal "github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors"
 )
 
 // AuthenticationFailedError indicates an authentication request has failed.
 type AuthenticationFailedError interface {
-	azcore.HTTPResponse
 	errorinfo.NonRetriable
+	RawResponse() *http.Response
 	authenticationFailed()
 }
 
@@ -50,7 +49,6 @@ func (e authenticationFailedError) RawResponse() *http.Response {
 }
 
 var _ AuthenticationFailedError = (*authenticationFailedError)(nil)
-var _ azcore.HTTPResponse = (*authenticationFailedError)(nil)
 var _ errorinfo.NonRetriable = (*authenticationFailedError)(nil)
 
 // CredentialUnavailableError indicates a credential can't attempt authenticate


Test patch:
diff --git a/eng/common/scripts/stress-testing/stress-test-deployment-lib.ps1 b/eng/common/scripts/stress-testing/stress-test-deployment-lib.ps1
index eb62546621..5ab1597043 100644
--- a/eng/common/scripts/stress-testing/stress-test-deployment-lib.ps1
+++ b/eng/common/scripts/stress-testing/stress-test-deployment-lib.ps1
@@ -40,12 +40,19 @@ function Login([string]$subscription, [string]$clusterGroup, [switch]$pushImages
     $cluster = RunOrExitOnFailure az aks list -g $clusterGroup --subscription $subscription -o json
     $clusterName = ($cluster | ConvertFrom-Json).name
 
+    $kubeContext = (RunOrExitOnFailure kubectl config view -o json) | ConvertFrom-Json
+    $defaultNamespace = $kubeContext.contexts.Where({ $_.name -eq $clusterName }).context.namespace
+
     RunOrExitOnFailure az aks get-credentials `
         -n "$clusterName" `
         -g "$clusterGroup" `
         --subscription "$subscription" `
         --overwrite-existing
 
+    if ($defaultNamespace) {
+        RunOrExitOnFailure kubectl config set-context $clusterName --namespace $defaultNamespace
+    }
+
     if ($pushImages) {
         $registry = RunOrExitOnFailure az acr list -g $clusterGroup --subscription $subscription -o json
         $registryName = ($registry | ConvertFrom-Json).name
diff --git a/sdk/azcore/arm/runtime/poller_test.go b/sdk/azcore/arm/runtime/poller_test.go
index 7fcdc25926..0a3bcb8143 100644
--- a/sdk/azcore/arm/runtime/poller_test.go
+++ b/sdk/azcore/arm/runtime/poller_test.go
@@ -15,6 +15,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/internal/pollers/async"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/internal/pollers/body"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/internal/pollers/loc"
@@ -42,14 +43,6 @@ type mockType struct {
 	Field *string `json:"field,omitempty"`
 }
 
-type mockError struct {
-	Msg string `json:"error"`
-}
-
-func (m mockError) Error() string {
-	return m.Msg
-}
-
 func getPipeline(srv *mock.Server) pipeline.Pipeline {
 	return runtime.NewPipeline(
 		"test",
@@ -59,14 +52,6 @@ func getPipeline(srv *mock.Server) pipeline.Pipeline {
 	)
 }
 
-func handleError(resp *http.Response) error {
-	var me mockError
-	if err := runtime.UnmarshalAsJSON(resp, &me); err != nil {
-		return err
-	}
-	return me
-}
-
 func initialResponse(method, u string, resp io.Reader) (*http.Response, mock.TrackedClose) {
 	req, err := http.NewRequest(method, u, nil)
 	if err != nil {
@@ -91,7 +76,7 @@ func TestNewPollerAsync(t *testing.T) {
 	resp.Header.Set(shared.HeaderAzureAsync, srv.URL())
 	resp.StatusCode = http.StatusCreated
 	pl := getPipeline(srv)
-	poller, err := NewPoller("pollerID", "", resp, pl, handleError)
+	poller, err := NewPoller("pollerID", "", resp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -105,7 +90,7 @@ func TestNewPollerAsync(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	poller, err = NewPollerFromResumeToken("pollerID", tk, pl, handleError)
+	poller, err = NewPollerFromResumeToken("pollerID", tk, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -127,7 +112,7 @@ func TestNewPollerBody(t *testing.T) {
 	resp, closed := initialResponse(http.MethodPatch, srv.URL(), strings.NewReader(provStateStarted))
 	resp.StatusCode = http.StatusCreated
 	pl := getPipeline(srv)
-	poller, err := NewPoller("pollerID", "", resp, pl, handleError)
+	poller, err := NewPoller("pollerID", "", resp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -141,7 +126,7 @@ func TestNewPollerBody(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	poller, err = NewPollerFromResumeToken("pollerID", tk, pl, handleError)
+	poller, err = NewPollerFromResumeToken("pollerID", tk, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -164,7 +149,7 @@ func TestNewPollerLoc(t *testing.T) {
 	resp.Header.Set(shared.HeaderLocation, srv.URL())
 	resp.StatusCode = http.StatusAccepted
 	pl := getPipeline(srv)
-	poller, err := NewPoller("pollerID", "", resp, pl, handleError)
+	poller, err := NewPoller("pollerID", "", resp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -178,7 +163,7 @@ func TestNewPollerLoc(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	poller, err = NewPollerFromResumeToken("pollerID", tk, pl, handleError)
+	poller, err = NewPollerFromResumeToken("pollerID", tk, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -203,7 +188,7 @@ func TestNewPollerInitialRetryAfter(t *testing.T) {
 	resp.Header.Set("Retry-After", "1")
 	resp.StatusCode = http.StatusCreated
 	pl := getPipeline(srv)
-	poller, err := NewPoller("pollerID", "", resp, pl, handleError)
+	poller, err := NewPoller("pollerID", "", resp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -232,7 +217,7 @@ func TestNewPollerCanceled(t *testing.T) {
 	resp.Header.Set(shared.HeaderAzureAsync, srv.URL())
 	resp.StatusCode = http.StatusCreated
 	pl := getPipeline(srv)
-	poller, err := NewPoller("pollerID", "", resp, pl, handleError)
+	poller, err := NewPoller("pollerID", "", resp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -261,7 +246,7 @@ func TestNewPollerFailedWithError(t *testing.T) {
 	resp.Header.Set(shared.HeaderAzureAsync, srv.URL())
 	resp.StatusCode = http.StatusCreated
 	pl := getPipeline(srv)
-	poller, err := NewPoller("pollerID", "", resp, pl, handleError)
+	poller, err := NewPoller("pollerID", "", resp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -276,7 +261,7 @@ func TestNewPollerFailedWithError(t *testing.T) {
 	if err == nil {
 		t.Fatal(err)
 	}
-	if _, ok := err.(mockError); !ok {
+	if _, ok := err.(*azcore.ResponseError); !ok {
 		t.Fatalf("unexpected error type %T", err)
 	}
 }
@@ -289,7 +274,7 @@ func TestNewPollerSuccessNoContent(t *testing.T) {
 	resp, closed := initialResponse(http.MethodPatch, srv.URL(), strings.NewReader(provStateStarted))
 	resp.StatusCode = http.StatusCreated
 	pl := getPipeline(srv)
-	poller, err := NewPoller("pollerID", "", resp, pl, handleError)
+	poller, err := NewPoller("pollerID", "", resp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -303,7 +288,7 @@ func TestNewPollerSuccessNoContent(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	poller, err = NewPollerFromResumeToken("pollerID", tk, pl, handleError)
+	poller, err = NewPollerFromResumeToken("pollerID", tk, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -323,7 +308,7 @@ func TestNewPollerFail202NoHeaders(t *testing.T) {
 	resp, closed := initialResponse(http.MethodDelete, srv.URL(), http.NoBody)
 	resp.StatusCode = http.StatusAccepted
 	pl := getPipeline(srv)
-	poller, err := NewPoller("pollerID", "", resp, pl, handleError)
+	poller, err := NewPoller("pollerID", "", resp, pl)
 	if err == nil {
 		t.Fatal("unexpected nil error")
 	}
diff --git a/sdk/azcore/example_test.go b/sdk/azcore/example_test.go
index a3763a1f97..689468cbcf 100644
--- a/sdk/azcore/example_test.go
+++ b/sdk/azcore/example_test.go
@@ -64,20 +64,20 @@ func ExampleNullValue() {
 	// {"count":null}
 }
 
-func ExampleHTTPResponse() {
+func ExampleResponseError() {
 	pipeline := runtime.NewPipeline("module", "version", runtime.PipelineOptions{}, nil)
 	req, err := runtime.NewRequest(context.Background(), "POST", "https://fakecontainerregisty.azurecr.io/acr/v1/nonexisteng/_tags")
 	if err != nil {
 		panic(err)
 	}
 	resp, err := pipeline.Do(req)
-	var httpErr azcore.HTTPResponse
-	if errors.As(err, &httpErr) {
+	var respErr *azcore.ResponseError
+	if errors.As(err, &respErr) {
 		// Handle Error
-		if httpErr.RawResponse().StatusCode == http.StatusNotFound {
-			fmt.Printf("Repository could not be found: %v", httpErr.RawResponse())
-		} else if httpErr.RawResponse().StatusCode == http.StatusForbidden {
-			fmt.Printf("You do not have permission to access this repository: %v", httpErr.RawResponse())
+		if respErr.StatusCode == http.StatusNotFound {
+			fmt.Printf("Repository could not be found: %v", respErr)
+		} else if respErr.StatusCode == http.StatusForbidden {
+			fmt.Printf("You do not have permission to access this repository: %v", respErr)
 		} else {
 			// ...
 		}
diff --git a/sdk/azcore/internal/pollers/poller_test.go b/sdk/azcore/internal/pollers/poller_test.go
index 0310bc5909..c7d260e49a 100644
--- a/sdk/azcore/internal/pollers/poller_test.go
+++ b/sdk/azcore/internal/pollers/poller_test.go
@@ -111,9 +111,7 @@ func TestNewPoller(t *testing.T) {
 		Header:     http.Header{},
 	}
 	firstResp.Header.Set(shared.HeaderRetryAfter, "1")
-	p := NewPoller(&fakePoller{Ep: srv.URL()}, firstResp, pl, func(*http.Response) error {
-		return errors.New("failed")
-	})
+	p := NewPoller(&fakePoller{Ep: srv.URL()}, firstResp, pl)
 	if p.Done() {
 		t.Fatal("unexpected done")
 	}
@@ -164,9 +162,7 @@ func TestNewPollerWithFinalGET(t *testing.T) {
 	firstResp := &http.Response{
 		StatusCode: http.StatusAccepted,
 	}
-	p := NewPoller(&fakePoller{Ep: srv.URL(), Fg: srv.URL()}, firstResp, pl, func(*http.Response) error {
-		return errors.New("failed")
-	})
+	p := NewPoller(&fakePoller{Ep: srv.URL(), Fg: srv.URL()}, firstResp, pl)
 	if p.Done() {
 		t.Fatal("unexpected done")
 	}
@@ -202,14 +198,14 @@ func TestNewPollerFail1(t *testing.T) {
 	firstResp := &http.Response{
 		StatusCode: http.StatusAccepted,
 	}
-	p := NewPoller(&fakePoller{Ep: srv.URL()}, firstResp, pl, func(*http.Response) error {
-		return errors.New("failed")
-	})
+	p := NewPoller(&fakePoller{Ep: srv.URL()}, firstResp, pl)
 	resp, err := p.PollUntilDone(context.Background(), time.Second, nil)
-	if err == nil {
-		t.Fatal("unexpected nil error")
-	} else if s := err.Error(); s != "failed" {
-		t.Fatalf("unexpected error %s", s)
+	var respErr *shared.ResponseError
+	if !errors.As(err, &respErr) {
+		t.Fatalf("unexpected error type %T", err)
+	}
+	if respErr.StatusCode != http.StatusConflict {
+		t.Fatalf("unexpected terminal status code %d", respErr.StatusCode)
 	}
 	if resp != nil {
 		t.Fatal("expected nil response")
@@ -225,14 +221,14 @@ func TestNewPollerFail2(t *testing.T) {
 	firstResp := &http.Response{
 		StatusCode: http.StatusAccepted,
 	}
-	p := NewPoller(&fakePoller{Ep: srv.URL()}, firstResp, pl, func(*http.Response) error {
-		return errors.New("failed")
-	})
+	p := NewPoller(&fakePoller{Ep: srv.URL()}, firstResp, pl)
 	resp, err := p.PollUntilDone(context.Background(), time.Second, nil)
-	if err == nil {
-		t.Fatal("unexpected nil error")
-	} else if s := err.Error(); s != "failed" {
-		t.Fatalf("unexpected error %s", s)
+	var respErr *shared.ResponseError
+	if !errors.As(err, &respErr) {
+		t.Fatalf("unexpected error type %T", err)
+	}
+	if respErr.StatusCode != http.StatusCreated {
+		t.Fatalf("unexpected terminal status code %d", respErr.StatusCode)
 	}
 	if resp != nil {
 		t.Fatal("expected nil response")
@@ -248,9 +244,7 @@ func TestNewPollerError(t *testing.T) {
 	firstResp := &http.Response{
 		StatusCode: http.StatusAccepted,
 	}
-	p := NewPoller(&fakePoller{Ep: srv.URL()}, firstResp, pl, func(*http.Response) error {
-		return errors.New("failed")
-	})
+	p := NewPoller(&fakePoller{Ep: srv.URL()}, firstResp, pl)
 	resp, err := p.PollUntilDone(context.Background(), time.Second, nil)
 	if err == nil {
 		t.Fatal("unexpected nil error")
diff --git a/sdk/azcore/internal/shared/response_error_test.go b/sdk/azcore/internal/shared/response_error_test.go
new file mode 100644
index 0000000000..8c4ea41d0d
--- /dev/null
+++ b/sdk/azcore/internal/shared/response_error_test.go
@@ -0,0 +1,385 @@
+//go:build go1.16
+// +build go1.16
+
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package shared
+
+import (
+	"errors"
+	"io"
+	"net/http"
+	"net/url"
+	"strings"
+	"testing"
+)
+
+func TestNewResponseErrorNoBodyNoErrorCode(t *testing.T) {
+	fakeURL, err := url.Parse("https://fakeurl.com/the/path?qp=removed")
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = NewResponseError(&http.Response{
+		Status:     "the system is down",
+		StatusCode: http.StatusInternalServerError,
+		Body:       http.NoBody,
+		Request: &http.Request{
+			Method: http.MethodGet,
+			URL:    fakeURL,
+		},
+	})
+	re, ok := err.(*ResponseError)
+	if !ok {
+		t.Fatalf("unexpected error type %T", err)
+	}
+	if re.ErrorCode != "" {
+		t.Fatal("expected empty error code")
+	}
+	if c := re.StatusCode; c != http.StatusInternalServerError {
+		t.Fatalf("unexpected status code %d", c)
+	}
+	const want = `GET https://fakeurl.com/the/path
+--------------------------------------------------------------------------------
+RESPONSE 500: the system is down
+ERROR CODE UNAVAILABLE
+--------------------------------------------------------------------------------
+Response contained no body
+--------------------------------------------------------------------------------
+`
+	if got := re.Error(); got != want {
+		t.Fatalf("\ngot:\n%s\nwant:\n%s\n", got, want)
+	}
+}
+
+func TestNewResponseErrorNoBody(t *testing.T) {
+	fakeURL, err := url.Parse("https://fakeurl.com/the/path?qp=removed")
+	if err != nil {
+		t.Fatal(err)
+	}
+	respHeader := http.Header{}
+	const errorCode = "ErrorTooManyCheats"
+	respHeader.Set("x-ms-error-code", errorCode)
+	err = NewResponseError(&http.Response{
+		Status:     "the system is down",
+		StatusCode: http.StatusInternalServerError,
+		Body:       http.NoBody,
+		Header:     respHeader,
+		Request: &http.Request{
+			Method: http.MethodGet,
+			URL:    fakeURL,
+		},
+	})
+	re, ok := err.(*ResponseError)
+	if !ok {
+		t.Fatalf("unexpected error type %T", err)
+	}
+	if ec := re.ErrorCode; ec != errorCode {
+		t.Fatalf("unexpected error code %s", ec)
+	}
+	if c := re.StatusCode; c != http.StatusInternalServerError {
+		t.Fatalf("unexpected status code %d", c)
+	}
+	const want = `GET https://fakeurl.com/the/path
+--------------------------------------------------------------------------------
+RESPONSE 500: the system is down
+ERROR CODE: ErrorTooManyCheats
+--------------------------------------------------------------------------------
+Response contained no body
+--------------------------------------------------------------------------------
+`
+	if got := re.Error(); got != want {
+		t.Fatalf("\ngot:\n%s\nwant:\n%s\n", got, want)
+	}
+}
+
+func TestNewResponseErrorNoErrorCode(t *testing.T) {
+	fakeURL, err := url.Parse("https://fakeurl.com/the/path?qp=removed")
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = NewResponseError(&http.Response{
+		Status:     "the system is down",
+		StatusCode: http.StatusInternalServerError,
+		Body:       io.NopCloser(strings.NewReader(`{ "code": "ErrorItsBroken", "message": "it's not working" }`)),
+		Request: &http.Request{
+			Method: http.MethodGet,
+			URL:    fakeURL,
+		},
+	})
+	re, ok := err.(*ResponseError)
+	if !ok {
+		t.Fatalf("unexpected error type %T", err)
+	}
+	if c := re.StatusCode; c != http.StatusInternalServerError {
+		t.Fatalf("unexpected status code %d", c)
+	}
+	const want = `GET https://fakeurl.com/the/path
+--------------------------------------------------------------------------------
+RESPONSE 500: the system is down
+ERROR CODE: ErrorItsBroken
+--------------------------------------------------------------------------------
+{
+  "code": "ErrorItsBroken",
+  "message": "it's not working"
+}
+--------------------------------------------------------------------------------
+`
+	if got := re.Error(); got != want {
+		t.Fatalf("\ngot:\n%s\nwant:\n%s\n", got, want)
+	}
+}
+
+func TestNewResponseErrorPreferErrorCodeHeader(t *testing.T) {
+	fakeURL, err := url.Parse("https://fakeurl.com/the/path?qp=removed")
+	if err != nil {
+		t.Fatal(err)
+	}
+	respHeader := http.Header{}
+	respHeader.Set("x-ms-error-code", "ErrorTooManyCheats")
+	err = NewResponseError(&http.Response{
+		Status:     "the system is down",
+		StatusCode: http.StatusInternalServerError,
+		Body:       io.NopCloser(strings.NewReader(`{ "code": "ErrorItsBroken", "message": "it's not working" }`)),
+		Header:     respHeader,
+		Request: &http.Request{
+			Method: http.MethodGet,
+			URL:    fakeURL,
+		},
+	})
+	re, ok := err.(*ResponseError)
+	if !ok {
+		t.Fatalf("unexpected error type %T", err)
+	}
+	if c := re.StatusCode; c != http.StatusInternalServerError {
+		t.Fatalf("unexpected status code %d", c)
+	}
+	const want = `GET https://fakeurl.com/the/path
+--------------------------------------------------------------------------------
+RESPONSE 500: the system is down
+ERROR CODE: ErrorTooManyCheats
+--------------------------------------------------------------------------------
+{
+  "code": "ErrorItsBroken",
+  "message": "it's not working"
+}
+--------------------------------------------------------------------------------
+`
+	if got := re.Error(); got != want {
+		t.Fatalf("\ngot:\n%s\nwant:\n%s\n", got, want)
+	}
+}
+
+func TestNewResponseErrorNoErrorCodeWrappedError(t *testing.T) {
+	fakeURL, err := url.Parse("https://fakeurl.com/the/path?qp=removed")
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = NewResponseError(&http.Response{
+		Status:     "the system is down",
+		StatusCode: http.StatusInternalServerError,
+		Body:       io.NopCloser(strings.NewReader(`{ "error": { "code": "ErrorItsBroken", "message": "it's not working" } }`)),
+		Request: &http.Request{
+			Method: http.MethodGet,
+			URL:    fakeURL,
+		},
+	})
+	re, ok := err.(*ResponseError)
+	if !ok {
+		t.Fatalf("unexpected error type %T", err)
+	}
+	if c := re.StatusCode; c != http.StatusInternalServerError {
+		t.Fatalf("unexpected status code %d", c)
+	}
+	const want = `GET https://fakeurl.com/the/path
+--------------------------------------------------------------------------------
+RESPONSE 500: the system is down
+ERROR CODE: ErrorItsBroken
+--------------------------------------------------------------------------------
+{
+  "error": {
+    "code": "ErrorItsBroken",
+    "message": "it's not working"
+  }
+}
+--------------------------------------------------------------------------------
+`
+	if got := re.Error(); got != want {
+		t.Fatalf("\ngot:\n%s\nwant:\n%s\n", got, want)
+	}
+}
+
+func TestNewResponseErrorNoErrorCodeInvalidBody(t *testing.T) {
+	fakeURL, err := url.Parse("https://fakeurl.com/the/path?qp=removed")
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = NewResponseError(&http.Response{
+		Status:     "the system is down",
+		StatusCode: http.StatusInternalServerError,
+		Body:       io.NopCloser(strings.NewReader("JSON error string")),
+		Request: &http.Request{
+			Method: http.MethodGet,
+			URL:    fakeURL,
+		},
+	})
+	re, ok := err.(*ResponseError)
+	if !ok {
+		t.Fatalf("unexpected error type %T", err)
+	}
+	if c := re.StatusCode; c != http.StatusInternalServerError {
+		t.Fatalf("unexpected status code %d", c)
+	}
+	const want = `GET https://fakeurl.com/the/path
+--------------------------------------------------------------------------------
+RESPONSE 500: the system is down
+ERROR CODE UNAVAILABLE
+--------------------------------------------------------------------------------
+JSON error string
+--------------------------------------------------------------------------------
+`
+	if got := re.Error(); got != want {
+		t.Fatalf("\ngot:\n%s\nwant:\n%s\n", got, want)
+	}
+}
+
+type readFailer struct{}
+
+func (r *readFailer) Close() error {
+	return nil
+}
+
+func (r *readFailer) Read(p []byte) (int, error) {
+	return 0, errors.New("mock read failure")
+}
+
+func TestNewResponseErrorNoErrorCodeCantReadBody(t *testing.T) {
+	fakeURL, err := url.Parse("https://fakeurl.com/the/path?qp=removed")
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = NewResponseError(&http.Response{
+		Status:     "the system is down",
+		StatusCode: http.StatusInternalServerError,
+		Body:       &readFailer{},
+		Request: &http.Request{
+			Method: http.MethodGet,
+			URL:    fakeURL,
+		},
+	})
+	_, ok := err.(*ResponseError)
+	if ok {
+		t.Fatalf("unexpected error type %T", err)
+	}
+	const want = `mock read failure`
+	if got := err.Error(); got != want {
+		t.Fatalf("\ngot:\n%s\nwant:\n%s\n", got, want)
+	}
+}
+
+func TestNewResponseErrorNoErrorCodeXML(t *testing.T) {
+	fakeURL, err := url.Parse("https://fakeurl.com/the/path?qp=removed")
+	if err != nil {
+		t.Fatal(err)
+	}
+	err = NewResponseError(&http.Response{
+		Status:     "the system is down",
+		StatusCode: http.StatusInternalServerError,
+		Body:       io.NopCloser(strings.NewReader(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Error><Code>ContainerAlreadyExists</Code><Message>The specified container already exists.\nRequestId:73b2473b-c1c8-4162-97bb-dc171bff61c9\nTime:2021-12-13T19:45:40.679Z</Message></Error>`)),
+		Request: &http.Request{
+			Method: http.MethodGet,
+			URL:    fakeURL,
+		},
+	})
+	re, ok := err.(*ResponseError)
+	if !ok {
+		t.Fatalf("unexpected error type %T", err)
+	}
+	if c := re.StatusCode; c != http.StatusInternalServerError {
+		t.Fatalf("unexpected status code %d", c)
+	}
+	const want = `GET https://fakeurl.com/the/path
+--------------------------------------------------------------------------------
+RESPONSE 500: the system is down
+ERROR CODE: ContainerAlreadyExists
+--------------------------------------------------------------------------------
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Error><Code>ContainerAlreadyExists</Code><Message>The specified container already exists.\nRequestId:73b2473b-c1c8-4162-97bb-dc171bff61c9\nTime:2021-12-13T19:45:40.679Z</Message></Error>
+--------------------------------------------------------------------------------
+`
+	if got := re.Error(); got != want {
+		t.Fatalf("\ngot:\n%s\nwant:\n%s\n", got, want)
+	}
+}
+
+func TestNewResponseErrorErrorCodeHeaderXML(t *testing.T) {
+	fakeURL, err := url.Parse("https://fakeurl.com/the/path?qp=removed")
+	if err != nil {
+		t.Fatal(err)
+	}
+	respHeader := http.Header{}
+	respHeader.Set("x-ms-error-code", "ContainerAlreadyExists")
+	err = NewResponseError(&http.Response{
+		Status:     "the system is down",
+		StatusCode: http.StatusInternalServerError,
+		Header:     respHeader,
+		Body:       io.NopCloser(strings.NewReader(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Error><Code>ContainerAlreadyExists</Code><Message>The specified container already exists.\nRequestId:73b2473b-c1c8-4162-97bb-dc171bff61c9\nTime:2021-12-13T19:45:40.679Z</Message></Error>`)),
+		Request: &http.Request{
+			Method: http.MethodGet,
+			URL:    fakeURL,
+		},
+	})
+	re, ok := err.(*ResponseError)
+	if !ok {
+		t.Fatalf("unexpected error type %T", err)
+	}
+	if c := re.StatusCode; c != http.StatusInternalServerError {
+		t.Fatalf("unexpected status code %d", c)
+	}
+	const want = `GET https://fakeurl.com/the/path
+--------------------------------------------------------------------------------
+RESPONSE 500: the system is down
+ERROR CODE: ContainerAlreadyExists
+--------------------------------------------------------------------------------
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Error><Code>ContainerAlreadyExists</Code><Message>The specified container already exists.\nRequestId:73b2473b-c1c8-4162-97bb-dc171bff61c9\nTime:2021-12-13T19:45:40.679Z</Message></Error>
+--------------------------------------------------------------------------------
+`
+	if got := re.Error(); got != want {
+		t.Fatalf("\ngot:\n%s\nwant:\n%s\n", got, want)
+	}
+}
+
+func TestNewResponseErrorAllMissingXML(t *testing.T) {
+	fakeURL, err := url.Parse("https://fakeurl.com/the/path?qp=removed")
+	if err != nil {
+		t.Fatal(err)
+	}
+	respHeader := http.Header{}
+	err = NewResponseError(&http.Response{
+		Status:     "the system is down",
+		StatusCode: http.StatusInternalServerError,
+		Header:     respHeader,
+		Body:       io.NopCloser(strings.NewReader(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Error><Message>The specified container already exists.\nRequestId:73b2473b-c1c8-4162-97bb-dc171bff61c9\nTime:2021-12-13T19:45:40.679Z</Message></Error>`)),
+		Request: &http.Request{
+			Method: http.MethodGet,
+			URL:    fakeURL,
+		},
+	})
+	re, ok := err.(*ResponseError)
+	if !ok {
+		t.Fatalf("unexpected error type %T", err)
+	}
+	if c := re.StatusCode; c != http.StatusInternalServerError {
+		t.Fatalf("unexpected status code %d", c)
+	}
+	const want = `GET https://fakeurl.com/the/path
+--------------------------------------------------------------------------------
+RESPONSE 500: the system is down
+ERROR CODE UNAVAILABLE
+--------------------------------------------------------------------------------
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Error><Message>The specified container already exists.\nRequestId:73b2473b-c1c8-4162-97bb-dc171bff61c9\nTime:2021-12-13T19:45:40.679Z</Message></Error>
+--------------------------------------------------------------------------------
+`
+	if got := re.Error(); got != want {
+		t.Fatalf("\ngot:\n%s\nwant:\n%s\n", got, want)
+	}
+}
diff --git a/sdk/azcore/internal/shared/shared_test.go b/sdk/azcore/internal/shared/shared_test.go
index bf70f094b7..b40818b34d 100644
--- a/sdk/azcore/internal/shared/shared_test.go
+++ b/sdk/azcore/internal/shared/shared_test.go
@@ -9,6 +9,7 @@ package shared
 import (
 	"context"
 	"errors"
+	"io"
 	"io/ioutil"
 	"net/http"
 	"strings"
@@ -23,33 +24,6 @@ func TestNopCloser(t *testing.T) {
 	}
 }
 
-type testError struct {
-	m string
-}
-
-func (t testError) Error() string {
-	return t.m
-}
-
-func TestNewResponseError(t *testing.T) {
-	err := NewResponseError(testError{m: "crash"}, &http.Response{StatusCode: http.StatusInternalServerError})
-	if s := err.Error(); s != "crash" {
-		t.Fatalf("unexpected error %s", s)
-	}
-	re, ok := err.(*ResponseError)
-	if !ok {
-		t.Fatalf("unexpected error type %T", err)
-	}
-	re.NonRetriable() // nop
-	if c := re.RawResponse().StatusCode; c != http.StatusInternalServerError {
-		t.Fatalf("unexpected status code %d", c)
-	}
-	var te testError
-	if !errors.As(err, &te) {
-		t.Fatal("unwrap failed")
-	}
-}
-
 func TestDelay(t *testing.T) {
 	if err := Delay(context.Background(), 5*time.Millisecond); err != nil {
 		t.Fatal(err)
@@ -128,3 +102,83 @@ func TestEndpointToScope(t *testing.T) {
 		t.Fatalf("unexpected scope %s", s)
 	}
 }
+
+func TestPayload(t *testing.T) {
+	const val = "payload"
+	resp := &http.Response{
+		Body: io.NopCloser(strings.NewReader(val)),
+	}
+	b, err := Payload(resp)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if string(b) != val {
+		t.Fatalf("got %s, want %s", string(b), val)
+	}
+	b, err = Payload(resp)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if string(b) != val {
+		t.Fatalf("got %s, want %s", string(b), val)
+	}
+}
+
+func TestNopClosingBytesReader(t *testing.T) {
+	const val1 = "the data"
+	ncbr := NewNopClosingBytesReader([]byte(val1))
+	b, err := io.ReadAll(ncbr)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if string(b) != val1 {
+		t.Fatalf("got %s, want %s", string(b), val1)
+	}
+	const val2 = "something else"
+	ncbr.Set([]byte(val2))
+	b, err = io.ReadAll(ncbr)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if string(b) != val2 {
+		t.Fatalf("got %s, want %s", string(b), val2)
+	}
+	if err = ncbr.Close(); err != nil {
+		t.Fatal(err)
+	}
+	// seek to beginning and read again
+	i, err := ncbr.Seek(0, io.SeekStart)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if i != 0 {
+		t.Fatalf("got %d, want %d", i, 0)
+	}
+	b, err = io.ReadAll(ncbr)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if string(b) != val2 {
+		t.Fatalf("got %s, want %s", string(b), val2)
+	}
+	// seek to middle from the end
+	i, err = ncbr.Seek(-4, io.SeekEnd)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if l := int64(len(val2)) - 4; i != l {
+		t.Fatalf("got %d, want %d", l, i)
+	}
+	b, err = io.ReadAll(ncbr)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if string(b) != "else" {
+		t.Fatalf("got %s, want %s", string(b), "else")
+	}
+	// underflow
+	_, err = ncbr.Seek(-int64(len(val2)+1), io.SeekCurrent)
+	if err == nil {
+		t.Fatal("unexpected nil error")
+	}
+}
diff --git a/sdk/azcore/runtime/policy_body_download_test.go b/sdk/azcore/runtime/policy_body_download_test.go
index db0cf8675e..f72fd08e0e 100644
--- a/sdk/azcore/runtime/policy_body_download_test.go
+++ b/sdk/azcore/runtime/policy_body_download_test.go
@@ -13,6 +13,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
 	"github.com/Azure/azure-sdk-for-go/sdk/internal/mock"
 )
@@ -307,7 +308,7 @@ func TestReadBodyAfterSeek(t *testing.T) {
 	if string(payload) != message {
 		t.Fatal("incorrect payload")
 	}
-	nb, ok := resp.Body.(*nopClosingBytesReader)
+	nb, ok := resp.Body.(*shared.NopClosingBytesReader)
 	if !ok {
 		t.Fatalf("unexpected body type: %t", resp.Body)
 	}
diff --git a/sdk/azcore/runtime/poller_test.go b/sdk/azcore/runtime/poller_test.go
index 0d8340e101..bd65cf08fc 100644
--- a/sdk/azcore/runtime/poller_test.go
+++ b/sdk/azcore/runtime/poller_test.go
@@ -16,27 +16,12 @@ import (
 	"testing"
 	"time"
 
+	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers"
 	"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
 	"github.com/Azure/azure-sdk-for-go/sdk/internal/mock"
 )
 
-type pollerError struct {
-	Err string `json:"error"`
-}
-
-func (p pollerError) Error() string {
-	return p.Err
-}
-
-func errUnmarshall(resp *http.Response) error {
-	var pe pollerError
-	if err := UnmarshalAsJSON(resp, &pe); err != nil {
-		panic(err)
-	}
-	return pe
-}
-
 type widget struct {
 	Size int `json:"size"`
 }
@@ -46,7 +31,7 @@ func TestNewPollerFail(t *testing.T) {
 	p, err := NewPoller("fake.poller", &http.Response{
 		Body:       body,
 		StatusCode: http.StatusBadRequest,
-	}, newTestPipeline(nil), errUnmarshall)
+	}, newTestPipeline(nil))
 	if err == nil {
 		t.Fatal("unexpected nil error")
 	}
@@ -72,7 +57,7 @@ func TestNewPollerFromResumeTokenFail(t *testing.T) {
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			p, err := NewPollerFromResumeToken("fake.poller", test.token, newTestPipeline(nil), errUnmarshall)
+			p, err := NewPollerFromResumeToken("fake.poller", test.token, newTestPipeline(nil))
 			if err == nil {
 				t.Fatal("unexpected nil error")
 			}
@@ -100,7 +85,7 @@ func TestLocPollerSimple(t *testing.T) {
 	body, closed := mock.NewTrackedCloser(http.NoBody)
 	firstResp.Body = body
 	pl := newTestPipeline(&policy.ClientOptions{Transport: srv})
-	lro, err := NewPoller("fake.poller", firstResp, pl, errUnmarshall)
+	lro, err := NewPoller("fake.poller", firstResp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -133,7 +118,7 @@ func TestLocPollerWithWidget(t *testing.T) {
 	body, closed := mock.NewTrackedCloser(http.NoBody)
 	firstResp.Body = body
 	pl := newTestPipeline(&policy.ClientOptions{Transport: srv})
-	lro, err := NewPoller("fake.poller", firstResp, pl, errUnmarshall)
+	lro, err := NewPoller("fake.poller", firstResp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -170,7 +155,7 @@ func TestLocPollerCancelled(t *testing.T) {
 	body, closed := mock.NewTrackedCloser(http.NoBody)
 	firstResp.Body = body
 	pl := newTestPipeline(&policy.ClientOptions{Transport: srv})
-	lro, err := NewPoller("fake.poller", firstResp, pl, errUnmarshall)
+	lro, err := NewPoller("fake.poller", firstResp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -182,7 +167,7 @@ func TestLocPollerCancelled(t *testing.T) {
 	if err == nil {
 		t.Fatal("unexpected nil error")
 	}
-	if _, ok := err.(pollerError); !ok {
+	if _, ok := err.(*azcore.ResponseError); !ok {
 		t.Fatal("expected pollerError")
 	}
 	if resp != nil {
@@ -210,7 +195,7 @@ func TestLocPollerWithError(t *testing.T) {
 	body, closed := mock.NewTrackedCloser(http.NoBody)
 	firstResp.Body = body
 	pl := newTestPipeline(&policy.ClientOptions{Transport: srv, Retry: policy.RetryOptions{MaxRetries: -1}})
-	lro, err := NewPoller("fake.poller", firstResp, pl, errUnmarshall)
+	lro, err := NewPoller("fake.poller", firstResp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -250,7 +235,7 @@ func TestLocPollerWithResumeToken(t *testing.T) {
 	body, closed := mock.NewTrackedCloser(http.NoBody)
 	firstResp.Body = body
 	pl := newTestPipeline(&policy.ClientOptions{Transport: srv})
-	lro, err := NewPoller("fake.poller", firstResp, pl, errUnmarshall)
+	lro, err := NewPoller("fake.poller", firstResp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -278,7 +263,7 @@ func TestLocPollerWithResumeToken(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	lro, err = NewPollerFromResumeToken("fake.poller", tk, pl, errUnmarshall)
+	lro, err = NewPollerFromResumeToken("fake.poller", tk, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -307,7 +292,7 @@ func TestLocPollerWithTimeout(t *testing.T) {
 	body, closed := mock.NewTrackedCloser(http.NoBody)
 	firstResp.Body = body
 	pl := newTestPipeline(&policy.ClientOptions{Transport: srv})
-	lro, err := NewPoller("fake.poller", firstResp, pl, errUnmarshall)
+	lro, err := NewPoller("fake.poller", firstResp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -350,7 +335,7 @@ func TestOpPollerSimple(t *testing.T) {
 		},
 	}
 	pl := newTestPipeline(&policy.ClientOptions{Transport: srv})
-	lro, err := NewPoller("fake.poller", firstResp, pl, errUnmarshall)
+	lro, err := NewPoller("fake.poller", firstResp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -393,7 +378,7 @@ func TestOpPollerWithWidgetPUT(t *testing.T) {
 		},
 	}
 	pl := newTestPipeline(&policy.ClientOptions{Transport: srv})
-	lro, err := NewPoller("fake.poller", firstResp, pl, errUnmarshall)
+	lro, err := NewPoller("fake.poller", firstResp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -441,7 +426,7 @@ func TestOpPollerWithWidgetPOSTLocation(t *testing.T) {
 		},
 	}
 	pl := newTestPipeline(&policy.ClientOptions{Transport: srv})
-	lro, err := NewPoller("fake.poller", firstResp, pl, errUnmarshall)
+	lro, err := NewPoller("fake.poller", firstResp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -487,7 +472,7 @@ func TestOpPollerWithWidgetPOST(t *testing.T) {
 		},
 	}
 	pl := newTestPipeline(&policy.ClientOptions{Transport: srv})
-	lro, err := NewPoller("fake.poller", firstResp, pl, errUnmarshall)
+	lro, err := NewPoller("fake.poller", firstResp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -536,7 +521,7 @@ func TestOpPollerWithWidgetResourceLocation(t *testing.T) {
 		},
 	}
 	pl := newTestPipeline(&policy.ClientOptions{Transport: srv})
-	lro, err := NewPoller("fake.poller", firstResp, pl, errUnmarshall)
+	lro, err := NewPoller("fake.poller", firstResp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -581,7 +566,7 @@ func TestOpPollerWithResumeToken(t *testing.T) {
 		},
 	}
 	pl := newTestPipeline(&policy.ClientOptions{Transport: srv})
-	lro, err := NewPoller("fake.poller", firstResp, pl, errUnmarshall)
+	lro, err := NewPoller("fake.poller", firstResp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -609,7 +594,7 @@ func TestOpPollerWithResumeToken(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	lro, err = NewPollerFromResumeToken("fake.poller", tk, pl, errUnmarshall)
+	lro, err = NewPollerFromResumeToken("fake.poller", tk, pl)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -629,7 +614,7 @@ func TestNopPoller(t *testing.T) {
 	body, closed := mock.NewTrackedCloser(http.NoBody)
 	firstResp.Body = body
 	pl := newTestPipeline(nil)
-	lro, err := NewPoller("fake.poller", firstResp, pl, errUnmarshall)
+	lro, err := NewPoller("fake.poller", firstResp, pl)
 	if err != nil {
 		t.Fatal(err)
 	}