diff --git a/abstractions/dotnet/src/IRequestAdapter.cs b/abstractions/dotnet/src/IRequestAdapter.cs
index 61da556f49..b7980f6b10 100644
--- a/abstractions/dotnet/src/IRequestAdapter.cs
+++ b/abstractions/dotnet/src/IRequestAdapter.cs
@@ -2,6 +2,7 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------
+using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -29,41 +30,46 @@ public interface IRequestAdapter
///
/// The RequestInformation object to use for the HTTP request.
/// The response handler to use for the HTTP request instead of the default handler.
+ /// The error factories mapping to use in case of a failed request.
/// The to use for cancelling the requests.
/// The deserialized response model.
- Task SendAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, CancellationToken cancellationToken = default) where ModelType : IParsable;
+ Task SendAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, Dictionary> errorMapping = default, CancellationToken cancellationToken = default) where ModelType : IParsable;
///
/// Executes the HTTP request specified by the given RequestInformation and returns the deserialized response model collection.
///
/// The RequestInformation object to use for the HTTP request.
/// The response handler to use for the HTTP request instead of the default handler.
+ /// The error factories mapping to use in case of a failed request.
/// The to use for cancelling the requests.
/// The deserialized response model collection.
- Task> SendCollectionAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, CancellationToken cancellationToken = default) where ModelType : IParsable;
+ Task> SendCollectionAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, Dictionary> errorMapping = default, CancellationToken cancellationToken = default) where ModelType : IParsable;
///
/// Executes the HTTP request specified by the given RequestInformation and returns the deserialized primitive response model.
///
/// The RequestInformation object to use for the HTTP request.
/// The response handler to use for the HTTP request instead of the default handler.
+ /// The error factories mapping to use in case of a failed request.
/// The to use for cancelling the requests.
/// The deserialized primitive response model.
- Task SendPrimitiveAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, CancellationToken cancellationToken = default);
+ Task SendPrimitiveAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, Dictionary> errorMapping = default, CancellationToken cancellationToken = default);
///
/// Executes the HTTP request specified by the given RequestInformation and returns the deserialized primitive response model collection.
///
/// The RequestInformation object to use for the HTTP request.
/// The response handler to use for the HTTP request instead of the default handler.
+ /// The error factories mapping to use in case of a failed request.
/// The to use for cancelling the requests.
/// The deserialized primitive response model collection.
- Task> SendPrimitiveCollectionAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, CancellationToken cancellationToken = default);
+ Task> SendPrimitiveCollectionAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, Dictionary> errorMapping = default, CancellationToken cancellationToken = default);
///
/// Executes the HTTP request specified by the given RequestInformation with no return content.
///
/// The RequestInformation object to use for the HTTP request.
/// The response handler to use for the HTTP request instead of the default handler.
+ /// The error factories mapping to use in case of a failed request.
/// The to use for cancelling the requests.
/// A Task to await completion.
- Task SendNoContentAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, CancellationToken cancellationToken = default);
+ Task SendNoContentAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, Dictionary> errorMapping = default, CancellationToken cancellationToken = default);
///
/// The base url for every request.
///
diff --git a/abstractions/dotnet/src/serialization/IParseNode.cs b/abstractions/dotnet/src/serialization/IParseNode.cs
index 314c49f8ae..e96a2db892 100644
--- a/abstractions/dotnet/src/serialization/IParseNode.cs
+++ b/abstractions/dotnet/src/serialization/IParseNode.cs
@@ -99,6 +99,12 @@ public interface IParseNode
/// The model object value of the node.
T GetObjectValue() where T : IParsable;
///
+ /// Gets the resulting error from the node.
+ ///
+ /// The error object value of the node.
+ /// The error factory.
+ IParsable GetErrorValue(Func factory);
+ ///
/// Callback called before the node is deserialized.
///
Action OnBeforeAssignFieldValues { get; set; }
diff --git a/http/dotnet/httpclient/src/HttpClientRequestAdapter.cs b/http/dotnet/httpclient/src/HttpClientRequestAdapter.cs
index 95a82600cb..4d72652755 100644
--- a/http/dotnet/httpclient/src/HttpClientRequestAdapter.cs
+++ b/http/dotnet/httpclient/src/HttpClientRequestAdapter.cs
@@ -58,13 +58,15 @@ public ISerializationWriterFactory SerializationWriterFactory
///
/// The instance to send
/// The to use with the response
+ /// The error factories mapping to use in case of a failed request.
/// The to use for cancelling the request.
- public async Task> SendCollectionAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, CancellationToken cancellationToken = default) where ModelType : IParsable
+ public async Task> SendCollectionAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, Dictionary> errorMapping = default, CancellationToken cancellationToken = default) where ModelType : IParsable
{
var response = await GetHttpResponseMessage(requestInfo, cancellationToken);
requestInfo.Content?.Dispose();
if(responseHandler == null)
{
+ await ThrowFailedResponse(response, errorMapping);
var rootNode = await GetRootParseNode(response);
var result = rootNode.GetCollectionOfObjectValues();
return result;
@@ -77,13 +79,15 @@ public async Task> SendCollectionAsync(Request
///
/// The RequestInformation object to use for the HTTP request.
/// The response handler to use for the HTTP request instead of the default handler.
+ /// The error factories mapping to use in case of a failed request.
/// The to use for cancelling the request.
/// The deserialized primitive response model collection.
- public async Task> SendPrimitiveCollectionAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, CancellationToken cancellationToken = default) {
+ public async Task> SendPrimitiveCollectionAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, Dictionary> errorMapping = default, CancellationToken cancellationToken = default) {
var response = await GetHttpResponseMessage(requestInfo, cancellationToken);
requestInfo.Content?.Dispose();
if(responseHandler == null)
{
+ await ThrowFailedResponse(response, errorMapping);
var rootNode = await GetRootParseNode(response);
var result = rootNode.GetCollectionOfPrimitiveValues();
return result;
@@ -96,14 +100,16 @@ public async Task> SendPrimitiveCollectionAsync
/// The instance to send
/// The to use with the response
+ /// The error factories mapping to use in case of a failed request.
/// The to use for cancelling the request.
/// The deserialized response model.
- public async Task SendAsync(RequestInformation requestInfo, IResponseHandler responseHandler = null, CancellationToken cancellationToken = default) where ModelType : IParsable
+ public async Task SendAsync(RequestInformation requestInfo, IResponseHandler responseHandler = null, Dictionary> errorMapping = default, CancellationToken cancellationToken = default) where ModelType : IParsable
{
var response = await GetHttpResponseMessage(requestInfo, cancellationToken);
requestInfo.Content?.Dispose();
if(responseHandler == null)
{
+ await ThrowFailedResponse(response, errorMapping);
var rootNode = await GetRootParseNode(response);
var result = rootNode.GetObjectValue();
return result;
@@ -116,9 +122,10 @@ public async Task SendAsync(RequestInformation requestInfo
///
/// The instance to send
/// The to use with the response
+ /// The error factories mapping to use in case of a failed request.
/// The to use for cancelling the request.
/// The deserialized primitive response model.
- public async Task SendPrimitiveAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, CancellationToken cancellationToken = default)
+ public async Task SendPrimitiveAsync(RequestInformation requestInfo, IResponseHandler responseHandler = default, Dictionary> errorMapping = default, CancellationToken cancellationToken = default)
{
var response = await GetHttpResponseMessage(requestInfo, cancellationToken);
requestInfo.Content?.Dispose();
@@ -131,6 +138,7 @@ public async Task SendPrimitiveAsync(RequestInformation re
}
else
{
+ await ThrowFailedResponse(response, errorMapping);
var rootNode = await GetRootParseNode(response);
object result;
if(modelType == typeof(bool))
@@ -173,9 +181,10 @@ public async Task SendPrimitiveAsync(RequestInformation re
///
/// The instance to send
/// The to use with the response
+ /// The error factories mapping to use in case of a failed request.
/// The to use for cancelling the request.
///
- public async Task SendNoContentAsync(RequestInformation requestInfo, IResponseHandler responseHandler = null, CancellationToken cancellationToken = default)
+ public async Task SendNoContentAsync(RequestInformation requestInfo, IResponseHandler responseHandler = null, Dictionary> errorMapping = default, CancellationToken cancellationToken = default)
{
var response = await GetHttpResponseMessage(requestInfo, cancellationToken);
requestInfo.Content?.Dispose();
@@ -184,6 +193,25 @@ public async Task SendNoContentAsync(RequestInformation requestInfo, IResponseHa
else
await responseHandler.HandleResponseAsync(response);
}
+ private async Task ThrowFailedResponse(HttpResponseMessage response, Dictionary> errorMapping)
+ {
+ if(response.IsSuccessStatusCode) return;
+
+ var statusCodeAsInt = (int)response.StatusCode;
+ var statusCodeAsString = statusCodeAsInt.ToString();
+ Func errorFactory;
+ if(errorMapping == null ||
+ !errorMapping.TryGetValue(statusCodeAsString, out errorFactory) &&
+ !(statusCodeAsInt >= 400 && statusCodeAsInt < 500 && errorMapping.TryGetValue("4XX", out errorFactory)) &&
+ !(statusCodeAsInt >= 500 && statusCodeAsInt < 600 && errorMapping.TryGetValue("5XX", out errorFactory)))
+ throw new HttpRequestException($"The server returned an unexpected status code and no error factory is registered for this code: {statusCodeAsString}");
+
+ var rootNode = await GetRootParseNode(response);
+ var result = rootNode.GetErrorValue(errorFactory);
+ if(result is not Exception ex)
+ throw new HttpRequestException($"The server returned an unexpected status code and the error registered for this code failed to deserialize: {statusCodeAsString}");
+ else throw ex;
+ }
private async Task GetRootParseNode(HttpResponseMessage response)
{
var responseContentType = response.Content.Headers?.ContentType?.MediaType?.ToLowerInvariant();
diff --git a/serialization/dotnet/json/src/JsonParseNode.cs b/serialization/dotnet/json/src/JsonParseNode.cs
index ec44a7228c..378196da94 100644
--- a/serialization/dotnet/json/src/JsonParseNode.cs
+++ b/serialization/dotnet/json/src/JsonParseNode.cs
@@ -256,12 +256,30 @@ public IEnumerable GetCollectionOfPrimitiveValues()
public T GetObjectValue() where T : IParsable
{
var item = (T)(typeof(T).GetConstructor(new Type[] { }).Invoke(new object[] { }));
+ return GetObjectValueInternal(item);
+ }
+ private T GetObjectValueInternal(T item) where T : IParsable
+ {
var fieldDeserializers = item.GetFieldDeserializers();
OnBeforeAssignFieldValues?.Invoke(item);
AssignFieldValues(item, fieldDeserializers);
OnAfterAssignFieldValues?.Invoke(item);
return item;
}
+ ///
+ /// Gets the resulting error from the node.
+ ///
+ /// The error object value of the node.
+ /// The error factory.
+ public IParsable GetErrorValue(Func factory)
+ {
+ if (factory == null) throw new ArgumentNullException(nameof(factory));
+
+ var instance = factory.Invoke();
+ if (instance == null) throw new InvalidOperationException("factory returned null");
+
+ return GetObjectValueInternal(instance);
+ }
private void AssignFieldValues(T item, IDictionary> fieldDeserializers) where T : IParsable
{
if(_jsonNode.ValueKind != JsonValueKind.Object) return;