Skip to content

Commit 9695551

Browse files
committed
introduce PlayerClientException
1 parent 77be4ae commit 9695551

File tree

3 files changed

+126
-12
lines changed

3 files changed

+126
-12
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace Beefweb.Client.Infrastructure;
2+
3+
internal sealed class ErrorResponse
4+
{
5+
public ErrorInfo? Error { get; set; }
6+
}
7+
8+
internal sealed class ErrorInfo
9+
{
10+
public string? Message { get; set; }
11+
12+
public string? Parameter { get; set; }
13+
}

src/Client/Infrastructure/RequestHandler.cs

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Globalization;
34
using System.IO;
5+
using System.Linq;
46
using System.Net;
57
using System.Net.Http;
68
using System.Net.Http.Headers;
@@ -18,6 +20,7 @@ internal sealed class RequestHandler : IRequestHandler
1820
{
1921
private static readonly Encoding Utf8 = new UTF8Encoding(false);
2022
private static readonly Type RawJsonType = typeof(RawJson);
23+
private static readonly Type ErrorResponseType = typeof(ErrorResponse);
2124
private static readonly byte[] EventPrefix = "data:"u8.ToArray();
2225

2326
internal static readonly JsonSerializerOptions DefaultSerializerOptions = CreateSerializerOptions();
@@ -68,7 +71,8 @@ private static JsonSerializerOptions CreateSerializerOptions()
6871
.SendAsync(request, HttpCompletionOption.ResponseContentRead, cancellationToken)
6972
.ConfigureAwait(false);
7073

71-
response.EnsureSuccessStatusCode();
74+
if (!response.IsSuccessStatusCode)
75+
await ThrowResponseError(response, cancellationToken).ConfigureAwait(false);
7276

7377
return await ParseResponse(response, returnType, serializerOptions, allowNullResponse, cancellationToken)
7478
.ConfigureAwait(false);
@@ -90,15 +94,13 @@ private static JsonSerializerOptions CreateSerializerOptions()
9094
return null;
9195
}
9296

93-
try
94-
{
95-
response.EnsureSuccessStatusCode();
97+
if (response.IsSuccessStatusCode)
9698
return new HttpStreamedResult(response);
97-
}
98-
catch
99+
100+
using (response)
99101
{
100-
response.Dispose();
101-
throw;
102+
await ThrowResponseError(response, cancellationToken).ConfigureAwait(false);
103+
return null;
102104
}
103105
}
104106

@@ -116,7 +118,8 @@ public async IAsyncEnumerable<object> GetEvents(
116118
.SendAsync(request, HttpCompletionOption.ResponseContentRead, cancellationToken)
117119
.ConfigureAwait(false);
118120

119-
response.EnsureSuccessStatusCode();
121+
if (!response.IsSuccessStatusCode)
122+
await ThrowResponseError(response, cancellationToken).ConfigureAwait(false);
120123

121124
var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
122125
await using var responseStreamScope = responseStream.ConfigureAwait(false);
@@ -151,7 +154,8 @@ public async IAsyncEnumerable<object> GetEvents(
151154
.SendAsync(request, HttpCompletionOption.ResponseContentRead, cancellationToken)
152155
.ConfigureAwait(false);
153156

154-
response.EnsureSuccessStatusCode();
157+
if (!response.IsSuccessStatusCode)
158+
await ThrowResponseError(response, cancellationToken).ConfigureAwait(false);
155159

156160
return returnType != null
157161
? await ParseResponse(response, returnType, serializerOptions, allowNullResponse, cancellationToken)
@@ -195,8 +199,62 @@ HttpContent GetContent()
195199
return result;
196200
}
197201

198-
private static InvalidDataException InvalidResponse()
202+
private static async ValueTask ThrowResponseError(
203+
HttpResponseMessage response, CancellationToken cancellationToken)
204+
{
205+
if (response.Content.Headers.ContentType?.MediaType != ContentTypes.Json)
206+
{
207+
throw CreateException(response);
208+
}
209+
210+
var errorResponse = (ErrorResponse?)await ParseResponse(
211+
response, ErrorResponseType, DefaultSerializerOptions, true, cancellationToken)
212+
.ConfigureAwait(false);
213+
214+
throw CreateException(response, errorResponse?.Error?.Message, errorResponse?.Error?.Parameter);
215+
}
216+
217+
private static PlayerClientException CreateException(
218+
HttpResponseMessage message,
219+
string? serverMessage = null,
220+
string? parameterName = null)
221+
{
222+
var messageBuilder = new StringBuilder(120);
223+
224+
messageBuilder.Append(
225+
CultureInfo.InvariantCulture,
226+
$"Response status code does not indicate success: {(int)message.StatusCode}");
227+
228+
if (message.ReasonPhrase != null)
229+
{
230+
messageBuilder.Append($" ({message.ReasonPhrase})");
231+
}
232+
233+
messageBuilder.Append('.');
234+
235+
if (serverMessage != null)
236+
{
237+
messageBuilder.Append(" Server error: ").Append(serverMessage);
238+
239+
if (!serverMessage.EndsWith('.'))
240+
messageBuilder.Append('.');
241+
}
242+
243+
if (parameterName != null)
244+
{
245+
messageBuilder.Append($" Parameter name: {parameterName}.");
246+
}
247+
248+
return new PlayerClientException(
249+
messageBuilder.ToString(),
250+
HttpRequestError.Unknown,
251+
message.StatusCode,
252+
serverMessage,
253+
parameterName);
254+
}
255+
256+
private static PlayerClientException InvalidResponse()
199257
{
200-
return new InvalidDataException("Invalid response: expected JSON object.");
258+
return new PlayerClientException("Invalid response: expected JSON object.", HttpRequestError.InvalidResponse);
201259
}
202260
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
using System.Net;
3+
using System.Net.Http;
4+
5+
namespace Beefweb.Client;
6+
7+
/// <summary>
8+
/// Exception for failed HTTP requests with context information.
9+
/// </summary>
10+
public class PlayerClientException : HttpRequestException
11+
{
12+
/// <summary>
13+
/// Server error message.
14+
/// </summary>
15+
public string? ServerErrorMessage { get; }
16+
17+
/// <summary>
18+
/// Name of the parameter which caused error.
19+
/// </summary>
20+
public string? ErrorParameterName { get; }
21+
22+
/// <summary>
23+
/// Creates new instance.
24+
/// </summary>
25+
/// <param name="message">Error message.</param>
26+
/// <param name="requestError">Error type.</param>
27+
/// <param name="statusCode">Status code.</param>
28+
/// <param name="serverErrorMessage">Server error message.</param>
29+
/// <param name="errorParameterName">Error parameter name.</param>
30+
/// <param name="inner">Inner exception</param>
31+
public PlayerClientException(
32+
string message,
33+
HttpRequestError requestError = HttpRequestError.Unknown,
34+
HttpStatusCode? statusCode = null,
35+
string? serverErrorMessage = null,
36+
string? errorParameterName = null,
37+
Exception? inner = null)
38+
: base(requestError, message, inner, statusCode)
39+
{
40+
ServerErrorMessage = serverErrorMessage;
41+
ErrorParameterName = errorParameterName;
42+
}
43+
}

0 commit comments

Comments
 (0)