From 3a5764f06880d99df7597e027f60337927d6cb91 Mon Sep 17 00:00:00 2001 From: Stephen Lautier Date: Wed, 24 Jul 2024 16:05:19 +0200 Subject: [PATCH] perf(logging): middleware loggings changed to compile-time logging + deprecated cleanup and minor imp (#85) ### Performance - **logging:** middleware loggings changed to compile-time logging - **request builder:** uri interpolation regex compile-time ### BREAKING CHANGES - **http request:** remove deprecated `FluentHttpRequest.Formatters` - **models:** change several options/context models to `record`'s --- CHANGELOG.md | 8 ++- .../Caching/HttpResponseSerializer.cs | 10 ++-- src/FluentlyHttpClient/FluentHttpClient.cs | 8 +-- .../FluentHttpClientBuilder.cs | 12 +--- .../FluentHttpClientOptions.cs | 10 ++-- src/FluentlyHttpClient/FluentHttpHeaders.cs | 37 ++++++------ .../FluentHttpMessageExtensions.cs | 6 +- src/FluentlyHttpClient/FluentHttpRequest.cs | 12 +--- .../FluentHttpRequestBuilder.cs | 32 ++++++----- src/FluentlyHttpClient/FluentHttpResponse.cs | 2 +- src/FluentlyHttpClient/FluentlyHttpClient.cs | 4 +- .../FluentlyHttpClient.csproj.DotSettings | 2 + .../HttpMessageExtensions.cs | 2 +- .../IFluentHttpHeaderBuilder.cs | 15 ++--- .../Middleware/FluentHttpMiddlewareBuilder.cs | 4 +- .../Middleware/FluentHttpMiddlewareConfig.cs | 49 +++------------- .../Middleware/FluentHttpMiddlewareRunner.cs | 6 +- .../Middleware/LoggerHttpMiddleware.cs | 57 +++++++++---------- .../Middleware/TimerHttpMiddleware.cs | 23 ++++---- .../RequestHashingExtensions.cs | 4 +- .../Utils/HttpExtensions.cs | 3 +- .../Utils/ObjectExtensions.cs | 3 +- .../Utils/QueryStringOptions.cs | 23 +------- .../Utils/QueryStringUtils.cs | 12 ++-- .../Utils/RegexExtensions.cs | 3 +- .../Utils/StringExtensions.cs | 1 - 26 files changed, 144 insertions(+), 204 deletions(-) create mode 100644 src/FluentlyHttpClient/FluentlyHttpClient.csproj.DotSettings diff --git a/CHANGELOG.md b/CHANGELOG.md index 89b40b9..9bfe00f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,15 +2,21 @@ [_vNext_](https://github.com/sketch7/FluentlyHttpClient/compare/3.8.1...3.9.0) (2020-X-X) -## [4.0.0](https://github.com/sketch7/FluentlyHttpClient/compare/3.9.6...4.0.0) (2024-07-23) +## [4.0.0](https://github.com/sketch7/FluentlyHttpClient/compare/3.9.6...4.0.0) (2024-07-24) ### Features - **http client builder:** configurable http version/policy via `WithVersion`, `WithVersionPolicy` - **http client builder:** defaults to http version http2.0 +### Performance +- **logging:** middleware loggings changed to compile-time logging +- **request builder:** uri interpolation regex compile-time + ### BREAKING CHANGES - **deps:** now target .net8 +- **http request:** remove deprecated `FluentHttpRequest.Formatters` +- **models:** change several options/context models to `record`'s ## [3.9.6](https://github.com/sketch7/FluentlyHttpClient/compare/3.9.5...3.9.6) (2024-06-11) diff --git a/src/FluentlyHttpClient/Caching/HttpResponseSerializer.cs b/src/FluentlyHttpClient/Caching/HttpResponseSerializer.cs index 6e2a152..70487ed 100644 --- a/src/FluentlyHttpClient/Caching/HttpResponseSerializer.cs +++ b/src/FluentlyHttpClient/Caching/HttpResponseSerializer.cs @@ -26,7 +26,7 @@ public class HttpResponseSerializer : IHttpResponseSerializer Hash = response.GetRequestHash(), ReasonPhrase = response.ReasonPhrase, StatusCode = (int)response.StatusCode, - Url = response.Message.RequestMessage.RequestUri.ToString(), + Url = response.Message.RequestMessage!.RequestUri!.ToString(), Version = response.Message.Version.ToString(), Headers = new(response.Headers), RequestMessage = response.Message.RequestMessage @@ -43,18 +43,18 @@ public class HttpResponseSerializer : IHttpResponseSerializer /// public Task Deserialize(IHttpResponseStore item) { - var contentType = new ContentType(item.ContentHeaders.ContentType); + var contentType = new ContentType(item.ContentHeaders!.ContentType!); var encoding = string.IsNullOrEmpty(contentType.CharSet) ? Encoding.UTF8 : Encoding.GetEncoding(contentType.CharSet); var cloned = new FluentHttpResponse(new((HttpStatusCode)item.StatusCode) { - Content = new StringContent(item.Content, encoding, contentType.MediaType), + Content = new StringContent(item.Content!, encoding, contentType.MediaType), ReasonPhrase = item.ReasonPhrase, - Version = new(item.Version), + Version = new(item.Version!), RequestMessage = item.RequestMessage, }); // todo: add items? - cloned.Headers.AddRange(item.Headers); + cloned.Headers.AddRange(item.Headers!); return Task.FromResult(cloned); } diff --git a/src/FluentlyHttpClient/FluentHttpClient.cs b/src/FluentlyHttpClient/FluentHttpClient.cs index a4db5c2..713fc0d 100644 --- a/src/FluentlyHttpClient/FluentHttpClient.cs +++ b/src/FluentlyHttpClient/FluentHttpClient.cs @@ -36,7 +36,7 @@ public interface IFluentHttpClient : IDisposable /// /// Gets the default formatter to be used when serializing body content. e.g. JSON, XML, etc... /// - MediaTypeFormatter DefaultFormatter { get; } + MediaTypeFormatter? DefaultFormatter { get; } /// Get the formatter for an HTTP content type. /// The HTTP content type (or null to automatically select one). @@ -102,7 +102,7 @@ public class FluentHttpClient : IFluentHttpClient public MediaTypeFormatterCollection Formatters { get; } /// - public MediaTypeFormatter DefaultFormatter { get; } + public MediaTypeFormatter? DefaultFormatter { get; } /// public HttpRequestHeaders Headers { get; } @@ -175,7 +175,7 @@ public FluentHttpRequestBuilder CreateRequest(string? uriTemplate = null, object /// public async Task Send(FluentHttpRequest request) { - if (request == null) throw new ArgumentNullException(nameof(request)); + ArgumentNullException.ThrowIfNull(request, nameof(request)); var requestId = request.Message.AddRequestId(); @@ -193,7 +193,7 @@ public async Task Send(FluentHttpRequest request) public async Task Send(HttpRequestMessage request) { - if (request == null) throw new ArgumentNullException(nameof(request)); + ArgumentNullException.ThrowIfNull(request, nameof(request)); var requestId = request.AddRequestId(); await RawHttpClient.SendAsync(request); diff --git a/src/FluentlyHttpClient/FluentHttpClientBuilder.cs b/src/FluentlyHttpClient/FluentHttpClientBuilder.cs index 45988ee..4aa9451 100644 --- a/src/FluentlyHttpClient/FluentHttpClientBuilder.cs +++ b/src/FluentlyHttpClient/FluentHttpClientBuilder.cs @@ -11,14 +11,14 @@ public class FluentHttpClientBuilder : IFluentHttpHeaderBuilder /// Gets the identifier specified. /// - public string? Identifier { get; private set; } + public string Identifier { get; private set; } = null!; private readonly IServiceProvider _serviceProvider; private readonly IFluentHttpClientFactory _fluentHttpClientFactory; private readonly FluentHttpMiddlewareBuilder _middlewareBuilder; private string? _baseUrl; private TimeSpan _timeout; - private readonly FluentHttpHeaders _headers = new(); + private readonly FluentHttpHeaders _headers = []; private Action? _requestBuilderDefaults; private HttpMessageHandler? _httpMessageHandler; private readonly FormatterOptions _formatterOptions = new(); @@ -73,42 +73,36 @@ public FluentHttpClientBuilder WithTimeout(TimeSpan timeout) return this; } - /// public FluentHttpClientBuilder WithHeader(string key, string value) { _headers.Set(key, value); return this; } - /// public FluentHttpClientBuilder WithHeader(string key, StringValues values) { _headers.Set(key, values); return this; } - /// public FluentHttpClientBuilder WithHeaders(IDictionary headers) { _headers.SetRange(headers); return this; } - /// public FluentHttpClientBuilder WithHeaders(IDictionary headers) { _headers.SetRange(headers); return this; } - /// public FluentHttpClientBuilder WithHeaders(IDictionary headers) { _headers.SetRange(headers); return this; } - /// public FluentHttpClientBuilder WithHeaders(FluentHttpHeaders headers) { _headers.SetRange(headers); @@ -287,7 +281,7 @@ internal class MediaTypeFormatterComparer : IEqualityComparer x?.GetType() == y?.GetType(); + public bool Equals(MediaTypeFormatter? x, MediaTypeFormatter? y) => x?.GetType() == y?.GetType(); public int GetHashCode(MediaTypeFormatter obj) => obj.GetType().GetHashCode(); } \ No newline at end of file diff --git a/src/FluentlyHttpClient/FluentHttpClientOptions.cs b/src/FluentlyHttpClient/FluentHttpClientOptions.cs index 6d78d32..f56b744 100644 --- a/src/FluentlyHttpClient/FluentHttpClientOptions.cs +++ b/src/FluentlyHttpClient/FluentHttpClientOptions.cs @@ -26,17 +26,17 @@ public class FluentHttpClientOptions /// /// Gets or sets the identifier (key) for the HTTP client. /// - public string? Identifier { get; set; } + public string Identifier { get; set; } = null!; /// /// Gets or sets the headers which should be sent with each request. /// - public FluentHttpHeaders? Headers { get; set; } + public FluentHttpHeaders Headers { get; set; } = null!; /// /// Gets or sets the middleware builder. /// - public FluentHttpMiddlewareBuilder MiddlewareBuilder { get; set; } + public FluentHttpMiddlewareBuilder MiddlewareBuilder { get; set; } = null!; /// /// Gets or sets handler to customize request on creation. In order to specify defaults as desired, or so. @@ -51,7 +51,7 @@ public class FluentHttpClientOptions /// /// Gets or sets formatters to be used for content negotiation, for "Accept" and body media formats. e.g. JSON, XML, etc... /// - public MediaTypeFormatterCollection? Formatters { get; set; } + public MediaTypeFormatterCollection Formatters { get; set; } = null!; /// /// Gets or sets the default formatter to be used for content negotiation body format. e.g. JSON, XML, etc... @@ -75,7 +75,7 @@ public FormatterOptions() /// /// Configure formatters to be used. /// - public MediaTypeFormatterCollection Formatters { get; } = new(); + public MediaTypeFormatterCollection Formatters { get; } = []; /// /// Set default formatter to be used when serializing body content and the preferred "Accept". diff --git a/src/FluentlyHttpClient/FluentHttpHeaders.cs b/src/FluentlyHttpClient/FluentHttpHeaders.cs index a637c30..f00eb6e 100644 --- a/src/FluentlyHttpClient/FluentHttpHeaders.cs +++ b/src/FluentlyHttpClient/FluentHttpHeaders.cs @@ -6,7 +6,7 @@ namespace FluentlyHttpClient; /// /// options. /// -public class FluentHttpHeadersOptions +public record FluentHttpHeadersOptions { /// /// Predicate function to exclude headers from being hashed in . @@ -43,9 +43,9 @@ public partial class FluentHttpHeaders : IFluentHttpHeaderBuilder _data = new(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary _data = new(StringComparer.OrdinalIgnoreCase); - public string[] this[string key] + public string[]? this[string key] { get => _data[key]; set => _data[key] = value; @@ -122,7 +122,7 @@ public FluentHttpHeaders(HttpHeaders headers) /// Header value to add. public FluentHttpHeaders Add(string header, string value) { - _data.Add(header, new[] { value }); + _data.Add(header, [value]); return this; } @@ -189,7 +189,7 @@ public FluentHttpHeaders AddRange(IDictionary> heade public FluentHttpHeaders AddRange(IDictionary headers) { foreach (var header in headers) - _data.Add(header.Key, new[] { header.Value }); + _data.Add(header.Key, [header.Value]); return this; } @@ -240,7 +240,7 @@ public StringValues Get(string header) /// Header value to add. public FluentHttpHeaders Set(string header, string value) { - this[header] = new[] { value }; + this[header] = [value]; return this; } @@ -374,11 +374,12 @@ public string ToHashString() /// /// Converts to dictionary. /// - public Dictionary ToDictionary() => _data; + public Dictionary ToDictionary() => _data; FluentHttpHeaders IFluentHttpHeaderBuilder.WithHeader(string key, string value) => Add(key, value); FluentHttpHeaders IFluentHttpHeaderBuilder.WithHeader(string key, StringValues values) => Add(key, values); FluentHttpHeaders IFluentHttpHeaderBuilder.WithHeaders(IDictionary headers) => SetRange(headers); + FluentHttpHeaders IFluentHttpHeaderBuilder.WithHeaders(IDictionary headers) => SetRange(headers); FluentHttpHeaders IFluentHttpHeaderBuilder.WithHeaders(IDictionary headers) => SetRange(headers); FluentHttpHeaders IFluentHttpHeaderBuilder.WithHeaders(FluentHttpHeaders headers) => SetRange(headers); } @@ -407,43 +408,43 @@ public StringValues AcceptLanguage /// /// Gets or sets the Authorization header. /// - public string Authorization + public string? Authorization { get => Get(HeaderTypes.Authorization); - set => this[HeaderTypes.Authorization] = new[] { value }; + set => this[HeaderTypes.Authorization] = [value]; } /// /// Gets or sets the Cache-Control header. /// - public string CacheControl + public string? CacheControl { get => Get(HeaderTypes.CacheControl); - set => this[HeaderTypes.CacheControl] = new[] { value }; + set => this[HeaderTypes.CacheControl] = [value]; } /// /// Gets or sets the Content-Type header. /// - public string ContentType + public string? ContentType { get => Get(HeaderTypes.ContentType); - set => this[HeaderTypes.ContentType] = new[] { value }; + set => this[HeaderTypes.ContentType] = [value]; } /// /// Gets or sets the User-Agent header. /// - public string UserAgent + public string? UserAgent { get => Get(HeaderTypes.UserAgent); - set => this[HeaderTypes.UserAgent] = new[] { value }; + set => this[HeaderTypes.UserAgent] = [value]; } /// /// Gets or sets the X-Forwarded-For header. /// - public StringValues XForwardedFor + public StringValues? XForwardedFor { get => Get(HeaderTypes.XForwardedFor); set => this[HeaderTypes.XForwardedFor] = value; @@ -452,9 +453,9 @@ public StringValues XForwardedFor /// /// Gets or sets the X-Forwarded-Host header. /// - public string XForwardedHost + public string? XForwardedHost { get => Get(HeaderTypes.XForwardedHost); - set => this[HeaderTypes.XForwardedHost] = new[] { value }; + set => this[HeaderTypes.XForwardedHost] = [value]; } } \ No newline at end of file diff --git a/src/FluentlyHttpClient/FluentHttpMessageExtensions.cs b/src/FluentlyHttpClient/FluentHttpMessageExtensions.cs index 63e6467..484a0f2 100644 --- a/src/FluentlyHttpClient/FluentHttpMessageExtensions.cs +++ b/src/FluentlyHttpClient/FluentHttpMessageExtensions.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; namespace FluentlyHttpClient; @@ -14,12 +14,12 @@ public static class FluentHttpMessageExtensions public static async Task Clone(this FluentHttpResponse response) { var contentString = await response.Content.ReadAsStringAsync(); - var contentType = response.Content.Headers.ContentType; + var contentType = response.Content.Headers.ContentType!; var encoding = string.IsNullOrEmpty(contentType.CharSet) ? Encoding.UTF8 : Encoding.GetEncoding(contentType.CharSet); var cloned = new FluentHttpResponse(new(response.StatusCode) { - Content = new StringContent(contentString, encoding, contentType.MediaType), + Content = new StringContent(contentString, encoding, contentType.MediaType!), ReasonPhrase = response.ReasonPhrase, Version = response.Message.Version, RequestMessage = response.Message.RequestMessage, diff --git a/src/FluentlyHttpClient/FluentHttpRequest.cs b/src/FluentlyHttpClient/FluentHttpRequest.cs index a6be21d..97b1ad8 100644 --- a/src/FluentlyHttpClient/FluentHttpRequest.cs +++ b/src/FluentlyHttpClient/FluentHttpRequest.cs @@ -1,4 +1,4 @@ -namespace FluentlyHttpClient; +namespace FluentlyHttpClient; /// /// Fluent HTTP request, which wraps the and add additional features. @@ -30,7 +30,7 @@ public HttpMethod Method /// /// Gets or sets the for the HTTP request. /// - public Uri Uri + public Uri? Uri { get => Message.RequestUri; set => Message.RequestUri = value; @@ -42,7 +42,7 @@ public Uri Uri public HttpRequestHeaders Headers => Message.Headers; /// - /// Determine whether has success status otherwise it will throw or not. + /// Determine whether it has success status otherwise it will throw or not. /// public bool HasSuccessStatusOrThrow { get; set; } @@ -54,12 +54,6 @@ public Uri Uri /// public IDictionary Items { get; protected set; } - /// - /// Formatters to be used for content negotiation for "Accept" and also sending formats. e.g. (JSON, XML) - /// - [Obsolete("This was added to be passed down to the middleware. Instead in middleware use FluentHttpMiddlewareClientContext.Formatters.")] - public MediaTypeFormatterCollection? Formatters { get; set; } // deprecated: remove - /// /// Initializes a new instance. /// diff --git a/src/FluentlyHttpClient/FluentHttpRequestBuilder.cs b/src/FluentlyHttpClient/FluentHttpRequestBuilder.cs index a8dc966..b452cfd 100644 --- a/src/FluentlyHttpClient/FluentHttpRequestBuilder.cs +++ b/src/FluentlyHttpClient/FluentHttpRequestBuilder.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Primitives; +using System.Diagnostics.CodeAnalysis; using System.Net; using System.Text.RegularExpressions; @@ -7,7 +8,7 @@ namespace FluentlyHttpClient; /// /// Class to build with a fluent API. /// -public class FluentHttpRequestBuilder : IFluentHttpHeaderBuilder, IFluentHttpMessageItems +public partial class FluentHttpRequestBuilder : IFluentHttpHeaderBuilder, IFluentHttpMessageItems { internal static Version _defaultVersion = HttpVersion.Version20; @@ -43,7 +44,7 @@ public FluentHttpHeaders Headers { get { - _headers ??= new(); + _headers ??= []; return _headers; } } @@ -66,7 +67,7 @@ public FluentHttpHeaders Headers private readonly IFluentHttpClient _fluentHttpClient; private HttpContent? _httpBody; - private static readonly Regex InterpolationRegex = new(@"\{(\w+)\}", RegexOptions.Compiled); + private static readonly Regex InterpolationRegex = InterpolationUriRegex(); private object? _queryParams; private bool _hasSuccessStatusOrThrow; private CancellationToken _cancellationToken; @@ -111,35 +112,36 @@ public FluentHttpRequestBuilder WithVersionPolicy(HttpVersionPolicy policy) return this; } - /// public FluentHttpRequestBuilder WithHeader(string key, string value) { Headers.Set(key, value); return this; } - /// public FluentHttpRequestBuilder WithHeader(string key, StringValues values) { Headers.Set(key, values); return this; } - /// public FluentHttpRequestBuilder WithHeaders(IDictionary headers) { Headers.SetRange(headers); return this; } - /// + public FluentHttpRequestBuilder WithHeaders(IDictionary headers) + { + Headers.SetRange(headers); + return this; + } + public FluentHttpRequestBuilder WithHeaders(IDictionary headers) { Headers.SetRange(headers); return this; } - /// public FluentHttpRequestBuilder WithHeaders(FluentHttpHeaders headers) { Headers.SetRange(headers); @@ -152,7 +154,7 @@ public FluentHttpRequestBuilder WithHeaders(FluentHttpHeaders headers) /// Uri resource template e.g. "/org/{id}" /// Data to interpolate within the Uri template place holders e.g. {id}. Can be either dictionary or object. /// Returns request builder for chaining. - public FluentHttpRequestBuilder WithUri(string uriTemplate, object? interpolationData = null) + public FluentHttpRequestBuilder WithUri([StringSyntax(StringSyntaxAttribute.Uri)] string uriTemplate, object? interpolationData = null) { UriTemplate = uriTemplate; Uri = interpolationData != null @@ -183,8 +185,8 @@ public FluentHttpRequestBuilder WithQueryParams(object queryParams, QueryStringO /// Returns request builder for chaining. public FluentHttpRequestBuilder WithQueryParams(object queryParams, Action configure) { - if (configure == null) throw new ArgumentNullException(nameof(configure)); - var options = _queryStringOptions?.Clone() ?? new QueryStringOptions(); + ArgumentNullException.ThrowIfNull(configure, nameof(configure)); + var options = _queryStringOptions == null ? new() : _queryStringOptions with { }; configure(options); return WithQueryParams(queryParams, options); } @@ -207,7 +209,7 @@ public FluentHttpRequestBuilder WithQueryParamsOptions(QueryStringOptions? optio /// Returns request builder for chaining. public FluentHttpRequestBuilder WithQueryParamsOptions(Action configure) { - if (configure == null) throw new ArgumentNullException(nameof(configure)); + ArgumentNullException.ThrowIfNull(configure, nameof(configure)); var options = _queryStringOptions ?? new QueryStringOptions(); configure(options); return WithQueryParamsOptions(options); @@ -357,8 +359,7 @@ public FluentHttpRequest Build() return new(this, httpRequest, Items) { HasSuccessStatusOrThrow = _hasSuccessStatusOrThrow, - CancellationToken = _cancellationToken, - Formatters = _fluentHttpClient.Formatters + CancellationToken = _cancellationToken }; } @@ -403,4 +404,7 @@ private static string BuildUri(string uri, object? queryParams, QueryStringOptio uri += $"?{queryString}"; return uri; } + + [GeneratedRegex(@"\{(\w+)\}", RegexOptions.Compiled)] + private static partial Regex InterpolationUriRegex(); } \ No newline at end of file diff --git a/src/FluentlyHttpClient/FluentHttpResponse.cs b/src/FluentlyHttpClient/FluentHttpResponse.cs index 0375b8f..4bfcd98 100644 --- a/src/FluentlyHttpClient/FluentHttpResponse.cs +++ b/src/FluentlyHttpClient/FluentHttpResponse.cs @@ -65,7 +65,7 @@ public HttpStatusCode StatusCode /// /// Gets or sets the reason phrase which typically is sent by the server together with the status code. /// - public string ReasonPhrase + public string? ReasonPhrase { get => Message.ReasonPhrase; set => Message.ReasonPhrase = value; diff --git a/src/FluentlyHttpClient/FluentlyHttpClient.cs b/src/FluentlyHttpClient/FluentlyHttpClient.cs index 2d11589..d28e13b 100644 --- a/src/FluentlyHttpClient/FluentlyHttpClient.cs +++ b/src/FluentlyHttpClient/FluentlyHttpClient.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; namespace FluentlyHttpClient; @@ -11,5 +11,5 @@ public static class FluentlyHttpClientMeta /// Gets the version of the FluentlyHttpClient library. /// public static Version Version = typeof(FluentlyHttpClientMeta).GetTypeInfo() - .Assembly.GetName().Version; + .Assembly.GetName().Version!; } \ No newline at end of file diff --git a/src/FluentlyHttpClient/FluentlyHttpClient.csproj.DotSettings b/src/FluentlyHttpClient/FluentlyHttpClient.csproj.DotSettings new file mode 100644 index 0000000..a74a2d7 --- /dev/null +++ b/src/FluentlyHttpClient/FluentlyHttpClient.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/src/FluentlyHttpClient/HttpMessageExtensions.cs b/src/FluentlyHttpClient/HttpMessageExtensions.cs index 9a0c222..3458656 100644 --- a/src/FluentlyHttpClient/HttpMessageExtensions.cs +++ b/src/FluentlyHttpClient/HttpMessageExtensions.cs @@ -1,4 +1,4 @@ -namespace FluentlyHttpClient; +namespace FluentlyHttpClient; internal static class HttpMessageExtensions { diff --git a/src/FluentlyHttpClient/IFluentHttpHeaderBuilder.cs b/src/FluentlyHttpClient/IFluentHttpHeaderBuilder.cs index c85c5eb..a56edaa 100644 --- a/src/FluentlyHttpClient/IFluentHttpHeaderBuilder.cs +++ b/src/FluentlyHttpClient/IFluentHttpHeaderBuilder.cs @@ -32,18 +32,13 @@ public interface IFluentHttpHeaderBuilder /// Returns client builder for chaining. T WithHeaders(IDictionary headers); - /// - /// Add the specified headers and their value for each request. - /// - /// Headers to add. - /// Returns client builder for chaining. + /// + T WithHeaders(IDictionary headers); + + /// T WithHeaders(IDictionary headers); - /// - /// Add the specified headers and their value for each request. - /// - /// Headers to add. - /// Returns client builder for chaining. + /// T WithHeaders(FluentHttpHeaders headers); } diff --git a/src/FluentlyHttpClient/Middleware/FluentHttpMiddlewareBuilder.cs b/src/FluentlyHttpClient/Middleware/FluentHttpMiddlewareBuilder.cs index 2f6be7a..ef93939 100644 --- a/src/FluentlyHttpClient/Middleware/FluentHttpMiddlewareBuilder.cs +++ b/src/FluentlyHttpClient/Middleware/FluentHttpMiddlewareBuilder.cs @@ -6,7 +6,7 @@ namespace FluentlyHttpClient.Middleware; public class FluentHttpMiddlewareBuilder { private readonly IServiceProvider _serviceProvider; - private readonly List _middleware = new(); + private readonly List _middleware = []; /// /// Gets the middleware count. @@ -82,7 +82,7 @@ public IFluentHttpMiddlewareRunner Build(IFluentHttpClient httpClient) { FluentHttpMiddlewareDelegate next = previous!.Invoke; if (pipe.Args == null) - ctor = new object[] { next, clientContext }; + ctor = [next, clientContext]; else { const int additionalCtorArgs = 2; diff --git a/src/FluentlyHttpClient/Middleware/FluentHttpMiddlewareConfig.cs b/src/FluentlyHttpClient/Middleware/FluentHttpMiddlewareConfig.cs index d9590de..352a72a 100644 --- a/src/FluentlyHttpClient/Middleware/FluentHttpMiddlewareConfig.cs +++ b/src/FluentlyHttpClient/Middleware/FluentHttpMiddlewareConfig.cs @@ -1,44 +1,13 @@ namespace FluentlyHttpClient.Middleware; /// -/// Middleware configuration. +/// HTTP Middleware configuration. /// -[DebuggerDisplay("{DebuggerDisplay,nq}")] -public class FluentHttpMiddlewareConfig -{ - /// - /// Debugger display. - /// - protected string DebuggerDisplay => $"Type: '{Type}', Args: '{Args}'"; - - /// - /// Gets or sets the type for the middleware. - /// - public Type Type { get; set; } - - /// - /// Gets or sets the arguments for the middleware. - /// - public object[]? Args { get; set; } - - /// - /// Initializes a new instance. - /// - public FluentHttpMiddlewareConfig() - { - } - - /// - /// Initializes a new instance. - /// - public FluentHttpMiddlewareConfig(Type type, object[]? args = null) - { - Type = type; - Args = args; - } - - /// - /// Destructuring. - /// - public void Deconstruct(out Type type, out object[] args) { type = Type; args = Args; } -} \ No newline at end of file +/// +/// Type for the middleware +/// Arguments for the middleware +public record FluentHttpMiddlewareConfig( + + Type Type, + object[]? Args = null +); diff --git a/src/FluentlyHttpClient/Middleware/FluentHttpMiddlewareRunner.cs b/src/FluentlyHttpClient/Middleware/FluentHttpMiddlewareRunner.cs index 85d455b..b0ea40f 100644 --- a/src/FluentlyHttpClient/Middleware/FluentHttpMiddlewareRunner.cs +++ b/src/FluentlyHttpClient/Middleware/FluentHttpMiddlewareRunner.cs @@ -1,10 +1,10 @@ -namespace FluentlyHttpClient.Middleware; +namespace FluentlyHttpClient.Middleware; /// /// Fluent HTTP middleware client context (per client). /// [DebuggerDisplay("{DebuggerDisplay,nq}")] -public class FluentHttpMiddlewareClientContext +public record FluentHttpMiddlewareClientContext { /// /// Debugger display. @@ -37,7 +37,7 @@ public FluentHttpMiddlewareClientContext(string identifier, MediaTypeFormatterCo /// Fluent HTTP middleware execution invoke context (per invoke). /// [DebuggerDisplay("{DebuggerDisplay,nq}")] -public class FluentHttpMiddlewareContext +public record FluentHttpMiddlewareContext { /// /// Debugger display. diff --git a/src/FluentlyHttpClient/Middleware/LoggerHttpMiddleware.cs b/src/FluentlyHttpClient/Middleware/LoggerHttpMiddleware.cs index b9720cd..c9dcc70 100644 --- a/src/FluentlyHttpClient/Middleware/LoggerHttpMiddleware.cs +++ b/src/FluentlyHttpClient/Middleware/LoggerHttpMiddleware.cs @@ -1,13 +1,15 @@ +using FluentlyHttpClient; using FluentlyHttpClient.Internal; using FluentlyHttpClient.Middleware; using Microsoft.Extensions.Logging; +using System.Net; namespace FluentlyHttpClient.Middleware { /// /// Logger HTTP middleware options. /// - public class LoggerHttpMiddlewareOptions + public record LoggerHttpMiddlewareOptions { /// /// Gets or sets whether the request log should be detailed e.g. include body. Note: This should only be enabled for development or as needed, @@ -66,45 +68,30 @@ public async Task Invoke(FluentHttpMiddlewareContext context && !options.ShouldLogDetailedResponse.GetValueOrDefault(false)) { response = await _next(context); - _logger.LogInformation("HTTP request [{method}] {requestUrl} responded {statusCode:D} in {elapsed:n0}ms", - request.Method, - request.Uri, - response.StatusCode, - watch.GetElapsedTime().TotalMilliseconds - ); + _logger.LoggerHttp_CondensedRequest(request.Method, request.Uri!, response.StatusCode, watch.GetElapsedTime().TotalMilliseconds); return response; } if (!(options.ShouldLogDetailedRequest ?? false)) - _logger.LogInformation("Pre-request... {request}", request); + _logger.LoggerHttp_Request(request); else { string? requestContent = null; if (request.Message.Content != null) requestContent = await request.Message.Content.ReadAsStringAsync(); - _logger.LogInformation( - "Pre-request... {request}\nHeaders: {headers}\nContent: {requestContent}", - request, - request.Headers.ToFormattedString(), - requestContent - ); + _logger.LoggerHttp_RequestDetailed(request, request.Headers.ToFormattedString(), requestContent); } response = await _next(context); var stopwatchElapsed = watch.GetElapsedTime(); if (response.Content == null || !(options.ShouldLogDetailedResponse ?? false)) { - _logger.LogInformation("Post-request... {response} in {elapsed:n0}ms", response, stopwatchElapsed.TotalMilliseconds); + _logger.LoggerHttp_Response(response, stopwatchElapsed.TotalMilliseconds); return response; } var responseContent = await response.Content.ReadAsStringAsync(); - _logger.LogInformation("Post-request... {response}\nHeaders: {headers}\nContent: {responseContent} in {elapsed:n0}ms", - response, - response.Headers.ToFormattedString(), - responseContent, - stopwatchElapsed.TotalMilliseconds - ); + _logger.LoggerHttp_ResponseDetailed(response, response.Headers.ToFormattedString(), responseContent, stopwatchElapsed.TotalMilliseconds); return response; } } @@ -131,11 +118,7 @@ public static FluentHttpRequestBuilder WithLoggingOptions(this FluentHttpRequest return requestBuilder; } - /// - /// Set logging options for the request. - /// - /// Request builder instance. - /// Action to configure logging options. + /// public static FluentHttpRequestBuilder WithLoggingOptions(this FluentHttpRequestBuilder requestBuilder, Action? configure) { var options = new LoggerHttpMiddlewareOptions(); @@ -169,9 +152,7 @@ public static FluentHttpRequestBuilder WithLoggingOptions(this FluentHttpRequest public static FluentHttpClientBuilder UseLogging(this FluentHttpClientBuilder builder, LoggerHttpMiddlewareOptions? options = null) => builder.UseMiddleware(options ?? new LoggerHttpMiddlewareOptions()); - /// - /// Use logger middleware which logs out going requests and incoming responses. - /// + /// /// Builder instance /// Action to configure logging options. public static FluentHttpClientBuilder UseLogging(this FluentHttpClientBuilder builder, Action? configure) @@ -181,4 +162,22 @@ public static FluentHttpClientBuilder UseLogging(this FluentHttpClientBuilder bu return builder.UseLogging(options); } } +} + +internal static partial class LogExtensions +{ + [LoggerMessage(LogLevel.Information, "HTTP request [{method}] {requestUrl} responded {statusCode:D} in {elapsed:n0}ms")] + internal static partial void LoggerHttp_CondensedRequest(this ILogger logger, HttpMethod method, Uri requestUrl, HttpStatusCode statusCode, double elapsed); + + [LoggerMessage(LogLevel.Information, "Pre - request... {request}")] + internal static partial void LoggerHttp_Request(this ILogger logger, FluentHttpRequest request); + + [LoggerMessage(LogLevel.Information, "Pre-request... {request}\nHeaders: {headers}\nContent: {requestContent}")] + internal static partial void LoggerHttp_RequestDetailed(this ILogger logger, FluentHttpRequest request, string headers, string? requestContent); + + [LoggerMessage(LogLevel.Information, "Post-request... {response} in {elapsed:n0}ms")] + internal static partial void LoggerHttp_Response(this ILogger logger, FluentHttpResponse response, double elapsed); + + [LoggerMessage(LogLevel.Information, "Post-request... {response}\nHeaders: {headers}\nContent: {responseContent} in {elapsed:n0}ms")] + internal static partial void LoggerHttp_ResponseDetailed(this ILogger logger, FluentHttpResponse response, string headers, string? responseContent, double elapsed); } \ No newline at end of file diff --git a/src/FluentlyHttpClient/Middleware/TimerHttpMiddleware.cs b/src/FluentlyHttpClient/Middleware/TimerHttpMiddleware.cs index 49cbaa1..572d13d 100644 --- a/src/FluentlyHttpClient/Middleware/TimerHttpMiddleware.cs +++ b/src/FluentlyHttpClient/Middleware/TimerHttpMiddleware.cs @@ -1,4 +1,5 @@ -using FluentlyHttpClient.Internal; +using FluentlyHttpClient; +using FluentlyHttpClient.Internal; using FluentlyHttpClient.Middleware; using Microsoft.Extensions.Logging; @@ -7,7 +8,7 @@ namespace FluentlyHttpClient.Middleware /// /// Timer HTTP middleware options. /// - public class TimerHttpMiddlewareOptions + public record TimerHttpMiddlewareOptions { /// /// Gets or sets the threshold warning timespan in order to log as warning. @@ -20,14 +21,10 @@ public class TimerHttpMiddlewareOptions /// public class TimerHttpMiddleware : IFluentHttpMiddleware { - private const string TimeTakenMessage = "Executed request {request} in {elapsed:n0}ms"; private readonly FluentHttpMiddlewareDelegate _next; private readonly TimerHttpMiddlewareOptions _options; private readonly ILogger _logger; - /// - /// Initializes a new instance. - /// public TimerHttpMiddleware( FluentHttpMiddlewareDelegate next, FluentHttpMiddlewareClientContext context, @@ -60,9 +57,9 @@ public async Task Invoke(FluentHttpMiddlewareContext context { var threshold = request.GetTimerWarnThreshold() ?? _options.WarnThreshold; if (_logger.IsEnabled(LogLevel.Warning) && stopwatchElapsed > threshold) - _logger.LogWarning(TimeTakenMessage, request, stopwatchElapsed.TotalMilliseconds); + _logger.TimerHttp_TimeTaken(LogLevel.Warning, request, stopwatchElapsed.TotalMilliseconds); else if (_logger.IsEnabled(LogLevel.Debug)) - _logger.LogDebug(TimeTakenMessage, request, stopwatchElapsed.TotalMilliseconds); + _logger.TimerHttp_TimeTaken(LogLevel.Debug, request, stopwatchElapsed.TotalMilliseconds); } return response.SetTimeTaken(stopwatchElapsed); @@ -134,9 +131,7 @@ public static TimeSpan GetTimeTaken(this FluentHttpResponse response) public static FluentHttpClientBuilder UseTimer(this FluentHttpClientBuilder builder, TimerHttpMiddlewareOptions? options = null) => builder.UseMiddleware(options ?? new TimerHttpMiddlewareOptions()); - /// - /// Use timer middleware which measures how long the request takes. - /// + /// /// Builder instance /// Action to configure timer options. public static FluentHttpClientBuilder UseTimer(this FluentHttpClientBuilder builder, Action? configure) @@ -146,4 +141,10 @@ public static FluentHttpClientBuilder UseTimer(this FluentHttpClientBuilder buil return builder.UseTimer(options); } } +} + +internal static partial class LogExtensions +{ + [LoggerMessage("Executed request {request} in {elapsed:n0}ms")] + internal static partial void TimerHttp_TimeTaken(this ILogger logger, LogLevel level, FluentHttpRequest request, double elapsed); } \ No newline at end of file diff --git a/src/FluentlyHttpClient/RequestHashingExtensions.cs b/src/FluentlyHttpClient/RequestHashingExtensions.cs index 4bbf8bc..65f32de 100644 --- a/src/FluentlyHttpClient/RequestHashingExtensions.cs +++ b/src/FluentlyHttpClient/RequestHashingExtensions.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using System.Collections.Specialized; namespace FluentlyHttpClient; @@ -99,7 +99,7 @@ public static FluentHttpRequestBuilder WithRequestHashOptions(this FluentHttpReq /// Get response caching options for the request. /// /// Request to get options from. - public static RequestHashOptions GetRequestHashOptions(this FluentHttpRequest request) + public static RequestHashOptions? GetRequestHashOptions(this FluentHttpRequest request) { request.Items.TryGetValue(HashOptionsKey, out var result); return (RequestHashOptions)result; diff --git a/src/FluentlyHttpClient/Utils/HttpExtensions.cs b/src/FluentlyHttpClient/Utils/HttpExtensions.cs index 7bc5690..2206fa3 100644 --- a/src/FluentlyHttpClient/Utils/HttpExtensions.cs +++ b/src/FluentlyHttpClient/Utils/HttpExtensions.cs @@ -1,7 +1,6 @@ -using System.Collections.Specialized; +using System.Collections.Specialized; using System.Web; -// ReSharper disable once CheckNamespace namespace FluentlyHttpClient; /// diff --git a/src/FluentlyHttpClient/Utils/ObjectExtensions.cs b/src/FluentlyHttpClient/Utils/ObjectExtensions.cs index 198756c..ddc4350 100644 --- a/src/FluentlyHttpClient/Utils/ObjectExtensions.cs +++ b/src/FluentlyHttpClient/Utils/ObjectExtensions.cs @@ -1,8 +1,7 @@ -using System.Collections; +using System.Collections; using System.Reflection; using System.Runtime.Serialization; -// ReSharper disable once CheckNamespace namespace FluentlyHttpClient; /// diff --git a/src/FluentlyHttpClient/Utils/QueryStringOptions.cs b/src/FluentlyHttpClient/Utils/QueryStringOptions.cs index a71fc96..6e92775 100644 --- a/src/FluentlyHttpClient/Utils/QueryStringOptions.cs +++ b/src/FluentlyHttpClient/Utils/QueryStringOptions.cs @@ -1,8 +1,5 @@ using System.Web; -#pragma warning disable 618 // todo: remove after removing deprecated code - -// ReSharper disable once CheckNamespace namespace FluentlyHttpClient; /// @@ -25,7 +22,7 @@ public enum QueryStringCollectionMode /// Querystring formatting options. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] -public class QueryStringOptions +public record QueryStringOptions { private static readonly Func CamelCaseString = key => key.ToCamelCase(); /// @@ -50,14 +47,12 @@ public class QueryStringOptions /// /// Gets or sets the function to format a collection item. This will allow you to manipulate the value. /// - [Obsolete("Use WithValueFormatter instead.")] // deprecated: remove public Func? CollectionItemFormatter { get; set; } /// /// Gets or sets the function to format the key e.g. lowercase. /// - [Obsolete("Use WithKeyFormatter instead.")] // deprecated: make internal - public Func KeyFormatter { get; set; } = DefaultKeyFormatter; + internal Func KeyFormatter { get; set; } = DefaultKeyFormatter; internal Func? ValueFormatter { get; set; } @@ -68,7 +63,6 @@ public class QueryStringOptions /// /// Gets or sets the function to format a collection item. This will allow you to manipulate the value. /// - [Obsolete("Use WithValueFormatter instead. ValueFormatter will configure collection items and even props.")] // deprecated: remove public QueryStringOptions WithCollectionItemFormatter(Func configure) { CollectionItemFormatter = configure; @@ -112,17 +106,4 @@ public QueryStringOptions WithValueEncoder(Func configure) ValueEncoder = configure; return this; } - - /// - /// Clones options into a new instance. - /// - /// Returns new instance with the copied options. - public QueryStringOptions Clone() - => new() - { - CollectionMode = CollectionMode, - CollectionItemFormatter = CollectionItemFormatter, - KeyFormatter = KeyFormatter, - ValueFormatter = ValueFormatter, - }; } \ No newline at end of file diff --git a/src/FluentlyHttpClient/Utils/QueryStringUtils.cs b/src/FluentlyHttpClient/Utils/QueryStringUtils.cs index 78f3d74..1fd3e6b 100644 --- a/src/FluentlyHttpClient/Utils/QueryStringUtils.cs +++ b/src/FluentlyHttpClient/Utils/QueryStringUtils.cs @@ -1,8 +1,6 @@ using System.Collections; using System.Web; -#pragma warning disable 618 // todo: remove after removing deprecate attr -// ReSharper disable once CheckNamespace namespace FluentlyHttpClient; /// @@ -32,8 +30,8 @@ public static string ToQueryString(this IDictionary? continue; var key = options.KeyFormatter != null - ? options.KeyFormatter(item.Key!.ToString()) - : item.Key!.ToString(); + ? options.KeyFormatter(item.Key!.ToString()!) + : item.Key!.ToString()!; if (item.Value is string) { @@ -55,7 +53,7 @@ public static string ToQueryString(this IDictionary? return qs; string FormatValue(object value) - => options.ValueFormatter == null ? value.ToString() : options.ValueFormatter(value); + => options.ValueFormatter == null ? value.ToString()! : options.ValueFormatter(value); } /// @@ -66,7 +64,7 @@ string FormatValue(object value) /// Returns querystring. public static string ToQueryString(this IDictionary dict, Action configure) { - if (configure == null) throw new ArgumentNullException(nameof(configure)); + ArgumentNullException.ThrowIfNull(configure, nameof(configure)); var opts = new QueryStringOptions(); configure(opts); return dict.ToQueryString(opts); @@ -112,7 +110,7 @@ private static string BuildCollectionQueryString(string key, IEnumerable values, return qs; } - private static string AddQueryString(string key, string value, string uri, Func valueEncoder) + private static string AddQueryString(string key, string? value, string uri, Func valueEncoder) { if (string.IsNullOrEmpty(value)) return uri; diff --git a/src/FluentlyHttpClient/Utils/RegexExtensions.cs b/src/FluentlyHttpClient/Utils/RegexExtensions.cs index 2507ea0..ac647d4 100644 --- a/src/FluentlyHttpClient/Utils/RegexExtensions.cs +++ b/src/FluentlyHttpClient/Utils/RegexExtensions.cs @@ -1,6 +1,5 @@ -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; -// ReSharper disable once CheckNamespace namespace FluentlyHttpClient; /// diff --git a/src/FluentlyHttpClient/Utils/StringExtensions.cs b/src/FluentlyHttpClient/Utils/StringExtensions.cs index 4748d1a..dfb7c6d 100644 --- a/src/FluentlyHttpClient/Utils/StringExtensions.cs +++ b/src/FluentlyHttpClient/Utils/StringExtensions.cs @@ -1,4 +1,3 @@ -// ReSharper disable once CheckNamespace namespace FluentlyHttpClient; internal static class InternalStringExtensions