diff --git a/MetaBrainz.Common/HttpError.cs b/MetaBrainz.Common/HttpError.cs
index df4281e..586a2b3 100644
--- a/MetaBrainz.Common/HttpError.cs
+++ b/MetaBrainz.Common/HttpError.cs
@@ -14,68 +14,48 @@ namespace MetaBrainz.Common;
[PublicAPI]
public class HttpError : Exception {
- /// Creates a new HTTP error.
- /// The response to take the status code and reason from.
- [Obsolete($"Use {nameof(HttpError.FromResponse)} or {nameof(HttpError.FromResponseAsync)} instead.")]
- public HttpError(HttpResponseMessage response) : this(response.StatusCode, response.ReasonPhrase, response.Version) { }
-
- /// Creates a new HTTP error.
- /// The status code for the error.
- /// The reason phrase associated with the error.
- /// The exception that caused this one, if any.
- public HttpError(HttpStatusCode status, string? reason, Exception? cause = null) : base(null, cause) {
- this.Reason = reason;
- this.Status = status;
- }
-
/// Creates a new HTTP error.
/// The status code for the error.
/// The reason phrase associated with the error.
/// The HTTP message version.
+ ///
+ /// The message to use; if this is not specified or , a message will be constructed based on
+ /// , and .
+ ///
/// The exception that caused this one, if any.
- public HttpError(HttpStatusCode status, string? reason, Version version, Exception? cause = null) : base(null, cause) {
+ public HttpError(HttpStatusCode status, string? reason = null, Version? version = null, string? message = null,
+ Exception? cause = null) : base(HttpError.MessageFor(status, reason, version, message), cause) {
this.Reason = reason;
this.Status = status;
this.Version = version;
}
- /// The content (assumed to be text) of the response that triggered the error, if available.
+ /// The content (assumed to be text) of the error response, if available.
public string? Content { get; private init; }
- /// The content headers of the response that triggered the error, if available.
+ /// The content headers of the error response, if available.
public HttpContentHeaders? ContentHeaders { get; private init; }
- /// Gets a textual representation of the HTTP error.
- /// A textual representation of the HTTP error.
- public override string Message {
- get {
- var sb = new StringBuilder();
- sb.Append("HTTP");
- if (this.Version is not null) {
- sb.Append('/').Append(this.Version);
- }
- sb.Append(' ').Append((int) this.Status).Append(" (").Append(this.Status).Append(')');
- if (this.Reason is not null) {
- sb.Append(" '").Append(this.Reason).Append('\'');
- }
- return sb.ToString();
- }
- }
-
/// The reason phrase associated with the error.
public string? Reason { get; }
- /// The headers of the response that triggered the error, if available.
+ /// The headers of the request that provoked the error response, if available.
+ public HttpRequestHeaders? RequestHeaders { get; private init; }
+
+ /// The URI for the request that provoked the error response, if available.
+ public Uri? RequestUri { get; private init; }
+
+ /// The headers of the error response, if available.
public HttpResponseHeaders? ResponseHeaders { get; private init; }
/// The status code for the error.
public HttpStatusCode Status { get; }
- /// The HTTP message version from the response that triggered the error, if available.
+ /// The HTTP message version from the error response, if available.
public Version? Version { get; private init; }
/// Creates a new HTTP error based on an response message.
- /// The response message that triggered the error.
+ /// The response.
/// A new HTTP error containing information taken from the response message.
public static HttpError FromResponse(HttpResponseMessage response) => AsyncUtils.ResultOf(HttpError.FromResponseAsync(response));
@@ -85,14 +65,30 @@ public class HttpError : Exception {
/// A new HTTP error containing information taken from the response message.
public static async Task FromResponseAsync(HttpResponseMessage response,
CancellationToken cancellationToken = default) {
- // It's unfortunate that the headers are not easily copied (the classes do not have public constructors), so any changes to the
- // response after this method is called will be reflected in the error's properties.
return new HttpError(response.StatusCode, response.ReasonPhrase) {
Content = await response.GetStringContentAsync(cancellationToken),
- ContentHeaders = response.Content.Headers,
- ResponseHeaders = response.Headers,
+ ContentHeaders = HttpUtils.Copy(response.Content.Headers),
+ RequestHeaders = HttpUtils.Copy(response.RequestMessage?.Headers),
+ RequestUri = response.RequestMessage?.RequestUri,
+ ResponseHeaders = HttpUtils.Copy(response.Headers),
Version = response.Version,
};
}
+ private static string MessageFor(HttpStatusCode status, string? reason, Version? version, string? message) {
+ if (message is not null) {
+ return message;
+ }
+ var sb = new StringBuilder();
+ sb.Append("HTTP");
+ if (version is not null) {
+ sb.Append('/').Append(version);
+ }
+ sb.Append(' ').Append((int) status).Append(" (").Append(status).Append(')');
+ if (reason is not null) {
+ sb.Append(" '").Append(reason).Append('\'');
+ }
+ return sb.ToString();
+ }
+
}
diff --git a/MetaBrainz.Common/HttpUtils.cs b/MetaBrainz.Common/HttpUtils.cs
index d706418..cb08216 100644
--- a/MetaBrainz.Common/HttpUtils.cs
+++ b/MetaBrainz.Common/HttpUtils.cs
@@ -1,7 +1,6 @@
-#define TRACE
-
-using System;
+using System;
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Net.Http;
@@ -18,6 +17,54 @@ namespace MetaBrainz.Common;
[PublicAPI]
public static class HttpUtils {
+ /// Creates a copy of a set of HTTP content headers.
+ /// The headers to copy.
+ /// A new set of HTTP content headers, with the same contents as the set provided.
+ [return: NotNullIfNotNull(nameof(headers))]
+ public static HttpContentHeaders? Copy(HttpContentHeaders? headers) {
+ if (headers is null) {
+ return null;
+ }
+ // There is no way to construct a copy of HTTP headers directly at the moment (see dotnet/runtime#95912).
+ using var dummy = new ByteArrayContent(Array.Empty());
+ HttpUtils.Copy(headers, dummy.Headers);
+ return dummy.Headers;
+ }
+
+ private static void Copy(HttpHeaders from, HttpHeaders to) {
+ foreach (var (name, values) in from.NonValidated) {
+ to.TryAddWithoutValidation(name, values);
+ }
+ }
+
+ /// Creates a copy of a set of HTTP request headers.
+ /// The headers to copy.
+ /// A new set of HTTP request headers, with the same contents as the set provided.
+ [return: NotNullIfNotNull(nameof(headers))]
+ public static HttpRequestHeaders? Copy(HttpRequestHeaders? headers) {
+ if (headers is null) {
+ return null;
+ }
+ // There is no way to construct a copy of HTTP headers directly at the moment (see dotnet/runtime#95912).
+ using var dummy = new HttpRequestMessage();
+ HttpUtils.Copy(headers, dummy.Headers);
+ return dummy.Headers;
+ }
+
+ /// Creates a copy of a set of HTTP response headers.
+ /// The headers to copy.
+ /// A new set of HTTP response headers, with the same contents as the set provided.
+ [return: NotNullIfNotNull(nameof(headers))]
+ public static HttpResponseHeaders? Copy(HttpResponseHeaders? headers) {
+ if (headers is null) {
+ return null;
+ }
+ // There is no way to construct a copy of HTTP headers directly at the moment (see dotnet/runtime#95912).
+ using var dummy = new HttpResponseMessage();
+ HttpUtils.Copy(headers, dummy.Headers);
+ return dummy.Headers;
+ }
+
/// Create a user agent header containing the name and version of the assembly containing a particular type.
/// The type to use to determine the assembly name and version.
///
diff --git a/MetaBrainz.Common/MetaBrainz.Common.csproj b/MetaBrainz.Common/MetaBrainz.Common.csproj
index adc7a4e..5994183 100644
--- a/MetaBrainz.Common/MetaBrainz.Common.csproj
+++ b/MetaBrainz.Common/MetaBrainz.Common.csproj
@@ -11,7 +11,7 @@
2022, 2023
MetaBrainz.Common
MetaBrainz
- 2.1.1-pre
+ 3.0.0-pre
diff --git a/public-api/MetaBrainz.Common.net6.0.cs.md b/public-api/MetaBrainz.Common.net6.0.cs.md
index b505a78..3496b84 100644
--- a/public-api/MetaBrainz.Common.net6.0.cs.md
+++ b/public-api/MetaBrainz.Common.net6.0.cs.md
@@ -38,11 +38,15 @@ public class HttpError : System.Exception {
public get;
}
- string Message {
- public override get;
+ string? Reason {
+ public get;
}
- string? Reason {
+ System.Net.Http.Headers.HttpRequestHeaders? RequestHeaders {
+ public get;
+ }
+
+ System.Uri? RequestUri {
public get;
}
@@ -58,12 +62,7 @@ public class HttpError : System.Exception {
public get;
}
- [System.ObsoleteAttribute("Use FromResponse or FromResponseAsync instead.")]
- public HttpError(System.Net.Http.HttpResponseMessage response);
-
- public HttpError(System.Net.HttpStatusCode status, string? reason, System.Exception? cause = null);
-
- public HttpError(System.Net.HttpStatusCode status, string? reason, System.Version version, System.Exception? cause = null);
+ public HttpError(System.Net.HttpStatusCode status, string? reason = null, System.Version? version = null, string? message = null, System.Exception? cause = null);
public static HttpError FromResponse(System.Net.Http.HttpResponseMessage response);
@@ -81,6 +80,15 @@ public static class HttpUtils {
public const string UnknownAssemblyName = "*Unknown Assembly*";
+ [return: System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("headers")]
+ public static System.Net.Http.Headers.HttpContentHeaders? Copy(System.Net.Http.Headers.HttpContentHeaders? headers);
+
+ [return: System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("headers")]
+ public static System.Net.Http.Headers.HttpRequestHeaders? Copy(System.Net.Http.Headers.HttpRequestHeaders? headers);
+
+ [return: System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("headers")]
+ public static System.Net.Http.Headers.HttpResponseHeaders? Copy(System.Net.Http.Headers.HttpResponseHeaders? headers);
+
public static System.Net.Http.Headers.ProductInfoHeaderValue CreateUserAgentHeader();
public static System.Net.Http.HttpResponseMessage EnsureSuccessful(this System.Net.Http.HttpResponseMessage response);
diff --git a/public-api/MetaBrainz.Common.net8.0.cs.md b/public-api/MetaBrainz.Common.net8.0.cs.md
index a76b53e..87a532b 100644
--- a/public-api/MetaBrainz.Common.net8.0.cs.md
+++ b/public-api/MetaBrainz.Common.net8.0.cs.md
@@ -38,11 +38,15 @@ public class HttpError : System.Exception {
public get;
}
- string Message {
- public override get;
+ string? Reason {
+ public get;
}
- string? Reason {
+ System.Net.Http.Headers.HttpRequestHeaders? RequestHeaders {
+ public get;
+ }
+
+ System.Uri? RequestUri {
public get;
}
@@ -58,12 +62,7 @@ public class HttpError : System.Exception {
public get;
}
- [System.ObsoleteAttribute("Use FromResponse or FromResponseAsync instead.")]
- public HttpError(System.Net.Http.HttpResponseMessage response);
-
- public HttpError(System.Net.HttpStatusCode status, string? reason, System.Exception? cause = null);
-
- public HttpError(System.Net.HttpStatusCode status, string? reason, System.Version version, System.Exception? cause = null);
+ public HttpError(System.Net.HttpStatusCode status, string? reason = null, System.Version? version = null, string? message = null, System.Exception? cause = null);
public static HttpError FromResponse(System.Net.Http.HttpResponseMessage response);
@@ -81,6 +80,15 @@ public static class HttpUtils {
public const string UnknownAssemblyName = "*Unknown Assembly*";
+ [return: System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("headers")]
+ public static System.Net.Http.Headers.HttpContentHeaders? Copy(System.Net.Http.Headers.HttpContentHeaders? headers);
+
+ [return: System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("headers")]
+ public static System.Net.Http.Headers.HttpRequestHeaders? Copy(System.Net.Http.Headers.HttpRequestHeaders? headers);
+
+ [return: System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("headers")]
+ public static System.Net.Http.Headers.HttpResponseHeaders? Copy(System.Net.Http.Headers.HttpResponseHeaders? headers);
+
public static System.Net.Http.Headers.ProductInfoHeaderValue CreateUserAgentHeader();
public static System.Net.Http.HttpResponseMessage EnsureSuccessful(this System.Net.Http.HttpResponseMessage response);