From 4ddda247c8c2f9a2d0657d1f1f9384480c97083e Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Thu, 11 Jul 2024 19:37:24 +0200 Subject: [PATCH] Header value null check (#2241) * Throw an exception when adding a header with `null` value * Add multiple header values properly * Default headers should allow multiple values --- Directory.Packages.props | 14 +++---- gen/SourceGenerator/ImmutableGenerator.cs | 25 +++++++----- .../Authenticators/JwtAuthenticator.cs | 5 ++- .../Authenticators/OAuth/OAuthWorkflow.cs | 12 +++--- src/RestSharp/Ensure.cs | 7 ++-- src/RestSharp/Options/RestClientOptions.cs | 1 + src/RestSharp/Parameters/DefaultParameters.cs | 9 ++--- src/RestSharp/Parameters/HeaderParameter.cs | 11 ++++- src/RestSharp/Parameters/Parameter.cs | 40 +++++++++++++++++-- .../Parameters/ParametersCollection.cs | 20 +++++----- src/RestSharp/Parameters/RequestParameters.cs | 2 +- .../Parameters/UrlSegmentParameter.cs | 2 +- .../Request/HttpRequestMessageExtensions.cs | 12 +++--- src/RestSharp/Request/RequestHeaders.cs | 16 ++++---- src/RestSharp/Request/RestRequest.cs | 4 +- .../Request/RestRequestExtensions.Headers.cs | 19 ++++++++- src/RestSharp/Request/UriExtensions.cs | 2 +- src/RestSharp/Response/RestResponseBase.cs | 2 +- src/RestSharp/RestClient.cs | 17 ++++---- .../HttpHeadersTests.cs | 3 +- .../SampleClasses/twitter.cs | 8 ++-- test/RestSharp.Tests/RequestHeaderTests.cs | 35 +++++++++++----- 22 files changed, 172 insertions(+), 94 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 64df33b50..61c85f4c3 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -16,8 +16,8 @@ - - + + @@ -28,7 +28,7 @@ - + @@ -36,14 +36,14 @@ - + - + - - + + \ No newline at end of file diff --git a/gen/SourceGenerator/ImmutableGenerator.cs b/gen/SourceGenerator/ImmutableGenerator.cs index 54ea9101b..7576359bd 100644 --- a/gen/SourceGenerator/ImmutableGenerator.cs +++ b/gen/SourceGenerator/ImmutableGenerator.cs @@ -32,22 +32,24 @@ public void Execute(GeneratorExecutionContext context) { static string GenerateImmutableClass(TypeDeclarationSyntax mutableClass, Compilation compilation) { var containingNamespace = compilation.GetSemanticModel(mutableClass.SyntaxTree).GetDeclaredSymbol(mutableClass)!.ContainingNamespace; - - var namespaceName = containingNamespace.ToDisplayString(); - - var className = mutableClass.Identifier.Text; - - var usings = mutableClass.SyntaxTree.GetCompilationUnitRoot().Usings.Select(u => u.ToString()); + var namespaceName = containingNamespace.ToDisplayString(); + var className = mutableClass.Identifier.Text; + var usings = mutableClass.SyntaxTree.GetCompilationUnitRoot().Usings.Select(u => u.ToString()); var properties = GetDefinitions(SyntaxKind.SetKeyword) - .Select(prop => $" public {prop.Type} {prop.Identifier.Text} {{ get; }}") + .Select( + prop => { + var xml = prop.GetLeadingTrivia().FirstOrDefault(x => x.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia)).GetStructure(); + return $"/// {xml} public {prop.Type} {prop.Identifier.Text} {{ get; }}"; + } + ) .ToArray(); var props = GetDefinitions(SyntaxKind.SetKeyword).ToArray(); const string argName = "inner"; - var mutableProperties = props - .Select(prop => $" {prop.Identifier.Text} = {argName}.{prop.Identifier.Text};"); + + var mutableProperties = props.Select(prop => $" {prop.Identifier.Text} = {argName}.{prop.Identifier.Text};"); var constructor = $$""" public ReadOnly{{className}}({{className}} {{argName}}) { @@ -85,7 +87,8 @@ IEnumerable GetDefinitions(SyntaxKind kind) .OfType() .Where( prop => - prop.AccessorList!.Accessors.Any(accessor => accessor.Keyword.IsKind(kind)) && prop.AttributeLists.All(list => list.Attributes.All(attr => attr.Name.ToString() != "Exclude")) + prop.AccessorList!.Accessors.Any(accessor => accessor.Keyword.IsKind(kind)) && + prop.AttributeLists.All(list => list.Attributes.All(attr => attr.Name.ToString() != "Exclude")) ); } -} +} \ No newline at end of file diff --git a/src/RestSharp/Authenticators/JwtAuthenticator.cs b/src/RestSharp/Authenticators/JwtAuthenticator.cs index 1b90bdd60..cadbd2a64 100644 --- a/src/RestSharp/Authenticators/JwtAuthenticator.cs +++ b/src/RestSharp/Authenticators/JwtAuthenticator.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -namespace RestSharp.Authenticators; +namespace RestSharp.Authenticators; /// /// JSON WEB TOKEN (JWT) Authenticator class. @@ -26,7 +26,8 @@ public class JwtAuthenticator(string accessToken) : AuthenticatorBase(GetToken(a [PublicAPI] public void SetBearerToken(string accessToken) => Token = GetToken(accessToken); - static string GetToken(string accessToken) => Ensure.NotEmpty(accessToken, nameof(accessToken)).StartsWith("Bearer ") ? accessToken : $"Bearer {accessToken}"; + static string GetToken(string accessToken) + => Ensure.NotEmptyString(accessToken, nameof(accessToken)).StartsWith("Bearer ") ? accessToken : $"Bearer {accessToken}"; protected override ValueTask GetAuthenticationParameter(string accessToken) => new(new HeaderParameter(KnownHeaders.Authorization, accessToken)); diff --git a/src/RestSharp/Authenticators/OAuth/OAuthWorkflow.cs b/src/RestSharp/Authenticators/OAuth/OAuthWorkflow.cs index 9b7a41837..894bb24f9 100644 --- a/src/RestSharp/Authenticators/OAuth/OAuthWorkflow.cs +++ b/src/RestSharp/Authenticators/OAuth/OAuthWorkflow.cs @@ -48,7 +48,7 @@ sealed class OAuthWorkflow { /// Any existing, non-OAuth query parameters desired in the request /// public OAuthParameters BuildRequestTokenSignature(string method, WebPairCollection parameters) { - Ensure.NotEmpty(ConsumerKey, nameof(ConsumerKey)); + Ensure.NotEmptyString(ConsumerKey, nameof(ConsumerKey)); var allParameters = new WebPairCollection(); allParameters.AddRange(parameters); @@ -76,8 +76,8 @@ public OAuthParameters BuildRequestTokenSignature(string method, WebPairCollecti /// The HTTP method for the intended request /// Any existing, non-OAuth query parameters desired in the request public OAuthParameters BuildAccessTokenSignature(string method, WebPairCollection parameters) { - Ensure.NotEmpty(ConsumerKey, nameof(ConsumerKey)); - Ensure.NotEmpty(Token, nameof(Token)); + Ensure.NotEmptyString(ConsumerKey, nameof(ConsumerKey)); + Ensure.NotEmptyString(Token, nameof(Token)); var allParameters = new WebPairCollection(); allParameters.AddRange(parameters); @@ -105,8 +105,8 @@ public OAuthParameters BuildAccessTokenSignature(string method, WebPairCollectio /// The HTTP method for the intended request /// Any existing, non-OAuth query parameters desired in the request public OAuthParameters BuildClientAuthAccessTokenSignature(string method, WebPairCollection parameters) { - Ensure.NotEmpty(ConsumerKey, nameof(ConsumerKey)); - Ensure.NotEmpty(ClientUsername, nameof(ClientUsername)); + Ensure.NotEmptyString(ConsumerKey, nameof(ConsumerKey)); + Ensure.NotEmptyString(ClientUsername, nameof(ClientUsername)); var allParameters = new WebPairCollection(); allParameters.AddRange(parameters); @@ -127,7 +127,7 @@ public OAuthParameters BuildClientAuthAccessTokenSignature(string method, WebPai } public OAuthParameters BuildProtectedResourceSignature(string method, WebPairCollection parameters) { - Ensure.NotEmpty(ConsumerKey, nameof(ConsumerKey)); + Ensure.NotEmptyString(ConsumerKey, nameof(ConsumerKey)); var allParameters = new WebPairCollection(); allParameters.AddRange(parameters); diff --git a/src/RestSharp/Ensure.cs b/src/RestSharp/Ensure.cs index 5d985c860..dbdb56f9d 100644 --- a/src/RestSharp/Ensure.cs +++ b/src/RestSharp/Ensure.cs @@ -17,11 +17,10 @@ namespace RestSharp; static class Ensure { public static T NotNull(T? value, [InvokerParameterName] string name) => value ?? throw new ArgumentNullException(name); - public static string NotEmpty(string? value, [InvokerParameterName] string name) - => string.IsNullOrWhiteSpace(value) ? throw new ArgumentNullException(name) : value!; - public static string NotEmptyString(object? value, [InvokerParameterName] string name) { var s = value as string ?? value?.ToString(); - return string.IsNullOrWhiteSpace(s) ? throw new ArgumentNullException(name) : s!; + if (s == null) throw new ArgumentNullException(name); + + return string.IsNullOrWhiteSpace(s) ? throw new ArgumentException("Parameter cannot be an empty string", name) : s; } } \ No newline at end of file diff --git a/src/RestSharp/Options/RestClientOptions.cs b/src/RestSharp/Options/RestClientOptions.cs index 5e924d09f..74fde4ef0 100644 --- a/src/RestSharp/Options/RestClientOptions.cs +++ b/src/RestSharp/Options/RestClientOptions.cs @@ -220,6 +220,7 @@ public int MaxTimeout { /// /// Set to true to allow multiple default parameters with the same name. Default is false. + /// This setting doesn't apply to headers as multiple header values for the same key is allowed. /// public bool AllowMultipleDefaultParametersWithSameName { get; set; } diff --git a/src/RestSharp/Parameters/DefaultParameters.cs b/src/RestSharp/Parameters/DefaultParameters.cs index 8ba19ccdc..a65b41846 100644 --- a/src/RestSharp/Parameters/DefaultParameters.cs +++ b/src/RestSharp/Parameters/DefaultParameters.cs @@ -28,12 +28,11 @@ public sealed class DefaultParameters(ReadOnlyRestClientOptions options) : Param [MethodImpl(MethodImplOptions.Synchronized)] public DefaultParameters AddParameter(Parameter parameter) { if (parameter.Type == ParameterType.RequestBody) - throw new NotSupportedException( - "Cannot set request body using default parameters. Use Request.AddBody() instead." - ); + throw new NotSupportedException("Cannot set request body using default parameters. Use Request.AddBody() instead."); if (!options.AllowMultipleDefaultParametersWithSameName && - !MultiParameterTypes.Contains(parameter.Type) && + parameter.Type != ParameterType.HttpHeader && + !MultiParameterTypes.Contains(parameter.Type) && this.Any(x => x.Name == parameter.Name)) { throw new ArgumentException("A default parameters with the same name has already been added", nameof(parameter)); } @@ -70,4 +69,4 @@ public DefaultParameters ReplaceParameter(Parameter parameter) .AddParameter(parameter); static readonly ParameterType[] MultiParameterTypes = [ParameterType.QueryString, ParameterType.GetOrPost]; -} +} \ No newline at end of file diff --git a/src/RestSharp/Parameters/HeaderParameter.cs b/src/RestSharp/Parameters/HeaderParameter.cs index 55830a63f..1607eaeda 100644 --- a/src/RestSharp/Parameters/HeaderParameter.cs +++ b/src/RestSharp/Parameters/HeaderParameter.cs @@ -21,5 +21,14 @@ public record HeaderParameter : Parameter { /// /// Parameter name /// Parameter value - public HeaderParameter(string? name, string? value) : base(name, value, ParameterType.HttpHeader, false) { } + public HeaderParameter(string name, string value) + : base( + Ensure.NotEmptyString(name, nameof(name)), + Ensure.NotNull(value, nameof(value)), + ParameterType.HttpHeader, + false + ) { } + + public new string Name => base.Name!; + public new string Value => (string)base.Value!; } \ No newline at end of file diff --git a/src/RestSharp/Parameters/Parameter.cs b/src/RestSharp/Parameters/Parameter.cs index 93de83479..903b33402 100644 --- a/src/RestSharp/Parameters/Parameter.cs +++ b/src/RestSharp/Parameters/Parameter.cs @@ -12,32 +12,66 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System.Diagnostics; + namespace RestSharp; /// /// Parameter container for REST requests /// -public abstract record Parameter(string? Name, object? Value, ParameterType Type, bool Encode) { +[DebuggerDisplay($"{{{nameof(DebuggerDisplay)}()}}")] +public abstract record Parameter { + /// + /// Parameter container for REST requests + /// + protected Parameter(string? name, object? value, ParameterType type, bool encode) { + Name = name; + Value = value; + Type = type; + Encode = encode; + } + /// /// MIME content type of the parameter /// public ContentType ContentType { get; protected init; } = ContentType.Undefined; + public string? Name { get; } + public object? Value { get; } + public ParameterType Type { get; } + public bool Encode { get; } /// /// Return a human-readable representation of this parameter /// /// String - public sealed override string ToString() => Value == null ? $"{Name}" : $"{Name}={Value}"; + public sealed override string ToString() => Value == null ? $"{Name}" : $"{Name}={ValueString}"; + + protected virtual string ValueString => Value?.ToString() ?? "null"; public static Parameter CreateParameter(string? name, object? value, ParameterType type, bool encode = true) // ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault => type switch { ParameterType.GetOrPost => new GetOrPostParameter(Ensure.NotEmptyString(name, nameof(name)), value?.ToString(), encode), ParameterType.UrlSegment => new UrlSegmentParameter(Ensure.NotEmptyString(name, nameof(name)), value?.ToString()!, encode), - ParameterType.HttpHeader => new HeaderParameter(name, value?.ToString()), + ParameterType.HttpHeader => new HeaderParameter(name!, value?.ToString()!), ParameterType.QueryString => new QueryParameter(Ensure.NotEmptyString(name, nameof(name)), value?.ToString(), encode), _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) }; + + [PublicAPI] + public void Deconstruct(out string? name, out object? value, out ParameterType type, out bool encode) { + name = Name; + value = Value; + type = Type; + encode = Encode; + } + + /// + /// Assists with debugging by displaying in the debugger output + /// + /// + [UsedImplicitly] + protected string DebuggerDisplay() => $"{GetType().Name.Replace("Parameter", "")} {ToString()}"; } public record NamedParameter : Parameter { diff --git a/src/RestSharp/Parameters/ParametersCollection.cs b/src/RestSharp/Parameters/ParametersCollection.cs index 2d675820e..299f2d498 100644 --- a/src/RestSharp/Parameters/ParametersCollection.cs +++ b/src/RestSharp/Parameters/ParametersCollection.cs @@ -17,25 +17,27 @@ namespace RestSharp; -public abstract class ParametersCollection : IReadOnlyCollection { - protected readonly List Parameters = []; +public abstract class ParametersCollection : IReadOnlyCollection where T : Parameter { + protected readonly List Parameters = []; // public ParametersCollection(IEnumerable parameters) => _parameters.AddRange(parameters); - static readonly Func SearchPredicate = (p, name) + static readonly Func SearchPredicate = (p, name) => p.Name != null && p.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase); - public bool Exists(Parameter parameter) => Parameters.Any(p => SearchPredicate(p, parameter.Name) && p.Type == parameter.Type); + public bool Exists(T parameter) => Parameters.Any(p => SearchPredicate(p, parameter.Name) && p.Type == parameter.Type); - public Parameter? TryFind(string parameterName) => Parameters.FirstOrDefault(x => SearchPredicate(x, parameterName)); + public T? TryFind(string parameterName) => Parameters.FirstOrDefault(x => SearchPredicate(x, parameterName)); - public IEnumerable GetParameters(ParameterType parameterType) => Parameters.Where(x => x.Type == parameterType); - - public IEnumerable GetParameters() where T : class => Parameters.OfType(); + public IEnumerable GetParameters() where TParameter : class, T => Parameters.OfType(); - public IEnumerator GetEnumerator() => Parameters.GetEnumerator(); + public IEnumerator GetEnumerator() => Parameters.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public int Count => Parameters.Count; +} + +public abstract class ParametersCollection : ParametersCollection { + public IEnumerable GetParameters(ParameterType parameterType) => Parameters.Where(x => x.Type == parameterType); } \ No newline at end of file diff --git a/src/RestSharp/Parameters/RequestParameters.cs b/src/RestSharp/Parameters/RequestParameters.cs index 96d5c8a98..2d673f9bd 100644 --- a/src/RestSharp/Parameters/RequestParameters.cs +++ b/src/RestSharp/Parameters/RequestParameters.cs @@ -42,7 +42,7 @@ public ParametersCollection AddParameters(IEnumerable parameters) { } /// - /// Add parameters from another parameters collection + /// Add parameters from another parameter collection /// /// /// diff --git a/src/RestSharp/Parameters/UrlSegmentParameter.cs b/src/RestSharp/Parameters/UrlSegmentParameter.cs index 82d6dc2fd..b9da7752a 100644 --- a/src/RestSharp/Parameters/UrlSegmentParameter.cs +++ b/src/RestSharp/Parameters/UrlSegmentParameter.cs @@ -29,7 +29,7 @@ public partial record UrlSegmentParameter : NamedParameter { public UrlSegmentParameter(string name, string value, bool encode = true) : base( name, - RegexPattern.Replace(Ensure.NotEmpty(value, nameof(value)), "/"), + RegexPattern.Replace(Ensure.NotEmptyString(value, nameof(value)), "/"), ParameterType.UrlSegment, encode ) { } diff --git a/src/RestSharp/Request/HttpRequestMessageExtensions.cs b/src/RestSharp/Request/HttpRequestMessageExtensions.cs index 00601d2d5..923f9364f 100644 --- a/src/RestSharp/Request/HttpRequestMessageExtensions.cs +++ b/src/RestSharp/Request/HttpRequestMessageExtensions.cs @@ -20,16 +20,16 @@ namespace RestSharp; static class HttpRequestMessageExtensions { public static void AddHeaders(this HttpRequestMessage message, RequestHeaders headers) { - var headerParameters = headers.Parameters.Where(x => !KnownHeaders.IsContentHeader(x.Name!)); + var headerParameters = headers.Where(x => !KnownHeaders.IsContentHeader(x.Name)); - headerParameters.ForEach(x => AddHeader(x, message.Headers)); + headerParameters.GroupBy(x => x.Name).ForEach(x => AddHeader(x, message.Headers)); return; - void AddHeader(Parameter parameter, HttpHeaders httpHeaders) { - var parameterStringValue = parameter.Value!.ToString(); + void AddHeader(IGrouping group, HttpHeaders httpHeaders) { + var parameterStringValues = group.Select(x => x.Value); - httpHeaders.Remove(parameter.Name!); - httpHeaders.TryAddWithoutValidation(parameter.Name!, parameterStringValue); + httpHeaders.Remove(group.Key); + httpHeaders.TryAddWithoutValidation(group.Key, parameterStringValues); } } } \ No newline at end of file diff --git a/src/RestSharp/Request/RequestHeaders.cs b/src/RestSharp/Request/RequestHeaders.cs index 9458cad47..10677d6e3 100644 --- a/src/RestSharp/Request/RequestHeaders.cs +++ b/src/RestSharp/Request/RequestHeaders.cs @@ -19,19 +19,17 @@ namespace RestSharp; -class RequestHeaders { - public RequestParameters Parameters { get; } = new(); - +class RequestHeaders : ParametersCollection { public RequestHeaders AddHeaders(ParametersCollection parameters) { - Parameters.AddParameters(parameters.GetParameters()); + Parameters.AddRange(parameters.GetParameters()); return this; } // Add Accept header based on registered deserializers if the caller has set none. public RequestHeaders AddAcceptHeader(string[] acceptedContentTypes) { - if (Parameters.TryFind(KnownHeaders.Accept) == null) { + if (TryFind(KnownHeaders.Accept) == null) { var accepts = acceptedContentTypes.JoinToString(", "); - Parameters.AddParameter(new HeaderParameter(KnownHeaders.Accept, accepts)); + Parameters.Add(new(KnownHeaders.Accept, accepts)); } return this; @@ -46,13 +44,13 @@ public RequestHeaders AddCookieHeaders(Uri uri, CookieContainer? cookieContainer if (string.IsNullOrWhiteSpace(cookies)) return this; var newCookies = SplitHeader(cookies); - var existing = Parameters.GetParameters().FirstOrDefault(x => x.Name == KnownHeaders.Cookie); + var existing = GetParameters().FirstOrDefault(x => x.Name == KnownHeaders.Cookie); if (existing?.Value != null) { - newCookies = newCookies.Union(SplitHeader(existing.Value.ToString()!)); + newCookies = newCookies.Union(SplitHeader(existing.Value!)); } - Parameters.AddParameter(new HeaderParameter(KnownHeaders.Cookie, string.Join("; ", newCookies))); + Parameters.Add(new(KnownHeaders.Cookie, string.Join("; ", newCookies))); return this; diff --git a/src/RestSharp/Request/RestRequest.cs b/src/RestSharp/Request/RestRequest.cs index e0e696517..eaad89972 100644 --- a/src/RestSharp/Request/RestRequest.cs +++ b/src/RestSharp/Request/RestRequest.cs @@ -16,8 +16,8 @@ using RestSharp.Authenticators; using RestSharp.Extensions; using RestSharp.Interceptors; -// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global +// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global // ReSharper disable UnusedAutoPropertyAccessor.Global namespace RestSharp; @@ -190,7 +190,7 @@ public RestRequest(Uri resource, Method method = Method.Get) internal void IncreaseNumberOfAttempts() => Attempts++; /// - /// How many attempts were made to send this Request + /// The number of attempts that were made to send this request /// /// /// This number is incremented each time the RestClient sends the request. diff --git a/src/RestSharp/Request/RestRequestExtensions.Headers.cs b/src/RestSharp/Request/RestRequestExtensions.Headers.cs index dfd2d8b44..8091a1a50 100644 --- a/src/RestSharp/Request/RestRequestExtensions.Headers.cs +++ b/src/RestSharp/Request/RestRequestExtensions.Headers.cs @@ -17,6 +17,21 @@ namespace RestSharp; public static partial class RestRequestExtensions { + /// + /// Adds a header to the request. RestSharp will try to separate request and content headers when calling the resource. + /// + /// Request instance + /// Header name + /// Header values + /// + public static RestRequest AddHeader(this RestRequest request, string name, string[] values) { + foreach (var value in values) { + AddHeader(request, name, value); + } + + return request; + } + /// /// Adds a header to the request. RestSharp will try to separate request and content headers when calling the resource. /// @@ -41,7 +56,7 @@ public static RestRequest AddHeader(this RestRequest request, string name, T /// /// Adds or updates the request header. RestSharp will try to separate request and content headers when calling the resource. - /// Existing header with the same name will be replaced. + /// The existing header with the same name will be replaced. /// /// Request instance /// Header name @@ -54,7 +69,7 @@ public static RestRequest AddOrUpdateHeader(this RestRequest request, string nam /// /// Adds or updates the request header. RestSharp will try to separate request and content headers when calling the resource. - /// Existing header with the same name will be replaced. + /// The existing header with the same name will be replaced. /// /// Request instance /// Header name diff --git a/src/RestSharp/Request/UriExtensions.cs b/src/RestSharp/Request/UriExtensions.cs index f6e414f42..5aa9bd3cd 100644 --- a/src/RestSharp/Request/UriExtensions.cs +++ b/src/RestSharp/Request/UriExtensions.cs @@ -40,7 +40,7 @@ public static Uri AddQueryString(this Uri uri, string? query) { var absoluteUri = uri.AbsoluteUri; var separator = absoluteUri.Contains('?') ? "&" : "?"; - return new Uri($"{absoluteUri}{separator}{query}"); + return new($"{absoluteUri}{separator}{query}"); } public static UrlSegmentParamsValues GetUrlSegmentParamsValues( diff --git a/src/RestSharp/Response/RestResponseBase.cs b/src/RestSharp/Response/RestResponseBase.cs index 1ba9ec509..383895fb4 100644 --- a/src/RestSharp/Response/RestResponseBase.cs +++ b/src/RestSharp/Response/RestResponseBase.cs @@ -21,7 +21,7 @@ namespace RestSharp; /// /// Base class for common properties shared by RestResponse and RestResponse[[T]] /// -[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "()}")] +[DebuggerDisplay($"{{{nameof(DebuggerDisplay)}()}}")] public abstract class RestResponseBase { /// /// Default constructor diff --git a/src/RestSharp/RestClient.cs b/src/RestSharp/RestClient.cs index 0b3f58380..95fd432cb 100644 --- a/src/RestSharp/RestClient.cs +++ b/src/RestSharp/RestClient.cs @@ -74,8 +74,8 @@ public RestClient( } ConfigureSerializers(configureSerialization); - Options = new ReadOnlyRestClientOptions(options); - DefaultParameters = new DefaultParameters(Options); + Options = new(options); + DefaultParameters = new(Options); if (useClientFactory) { _disposeHttpClient = false; @@ -121,8 +121,7 @@ public RestClient( ConfigureHeaders? configureDefaultHeaders = null, ConfigureSerialization? configureSerialization = null, bool useClientFactory = false - ) - : this(ConfigureOptions(new RestClientOptions(), configureRestClient), configureDefaultHeaders, configureSerialization, useClientFactory) { } + ) : this(ConfigureOptions(new(), configureRestClient), configureDefaultHeaders, configureSerialization, useClientFactory) { } /// /// @@ -141,7 +140,7 @@ public RestClient( bool useClientFactory = false ) : this( - ConfigureOptions(new RestClientOptions { BaseUrl = baseUrl }, configureRestClient), + ConfigureOptions(new() { BaseUrl = baseUrl }, configureRestClient), configureDefaultHeaders, configureSerialization, useClientFactory @@ -185,8 +184,8 @@ public RestClient( } var opt = options ?? new RestClientOptions(); - Options = new ReadOnlyRestClientOptions(opt); - DefaultParameters = new DefaultParameters(Options); + Options = new(opt); + DefaultParameters = new(Options); if (options != null) { ConfigureHttpClient(httpClient, options); @@ -207,7 +206,7 @@ public RestClient( ConfigureRestClient? configureRestClient = null, ConfigureSerialization? configureSerialization = null ) - : this(httpClient, ConfigureOptions(new RestClientOptions(), configureRestClient), disposeHttpClient, configureSerialization) { } + : this(httpClient, ConfigureOptions(new(), configureRestClient), disposeHttpClient, configureSerialization) { } /// /// Creates a new instance of RestSharp using the message handler provided. By default, HttpClient disposes the provided handler @@ -270,7 +269,7 @@ void ConfigureSerializers(ConfigureSerialization? configureSerialization) { var serializerConfig = new SerializerConfig(); serializerConfig.UseDefaultSerializers(); configureSerialization?.Invoke(serializerConfig); - Serializers = new RestSerializers(serializerConfig); + Serializers = new(serializerConfig); AcceptedContentTypes = Serializers.GetAcceptedContentTypes(); } diff --git a/test/RestSharp.Tests.Integrated/HttpHeadersTests.cs b/test/RestSharp.Tests.Integrated/HttpHeadersTests.cs index 6d5618283..559c6435a 100644 --- a/test/RestSharp.Tests.Integrated/HttpHeadersTests.cs +++ b/test/RestSharp.Tests.Integrated/HttpHeadersTests.cs @@ -50,8 +50,7 @@ public async Task Should_use_both_default_and_request_headers() { _client.AddDefaultHeader(defaultHeader.Name, defaultHeader.Value); - var request = new RestRequest("/headers") - .AddHeader(requestHeader.Name, requestHeader.Value); + var request = new RestRequest("/headers").AddHeader(requestHeader.Name, requestHeader.Value); var response = await _client.ExecuteAsync(request); CheckHeader(response, defaultHeader); diff --git a/test/RestSharp.Tests.Serializers.Xml/SampleClasses/twitter.cs b/test/RestSharp.Tests.Serializers.Xml/SampleClasses/twitter.cs index 3a10375d9..1afe25fc3 100644 --- a/test/RestSharp.Tests.Serializers.Xml/SampleClasses/twitter.cs +++ b/test/RestSharp.Tests.Serializers.Xml/SampleClasses/twitter.cs @@ -1,14 +1,15 @@ using RestSharp.Serializers; + // ReSharper disable InconsistentNaming // ReSharper disable UnusedMember.Global // ReSharper disable ClassNeverInstantiated.Global +#pragma warning disable CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language. #pragma warning disable CS8981 -namespace RestSharp.Tests.Serializers.Xml.SampleClasses; +namespace RestSharp.Tests.Serializers.Xml.SampleClasses; #pragma warning disable CS8981 public class status { -#pragma warning restore CS8981 public bool truncated { get; set; } public string created_at { get; set; } @@ -113,4 +114,5 @@ public class complexStatus { public long id { get; set; } public string text { get; set; } -} \ No newline at end of file +} +#pragma warning restore CS8981 \ No newline at end of file diff --git a/test/RestSharp.Tests/RequestHeaderTests.cs b/test/RestSharp.Tests/RequestHeaderTests.cs index 8752dca10..6fa6c8215 100644 --- a/test/RestSharp.Tests/RequestHeaderTests.cs +++ b/test/RestSharp.Tests/RequestHeaderTests.cs @@ -21,7 +21,7 @@ public void AddHeaders_SameCaseDuplicatesExist_ThrowsException() { [Fact] public void AddHeaders_DifferentCaseDuplicatesExist_ThrowsException() { var headers = _headers; - headers.Add(new KeyValuePair(KnownHeaders.Accept, ContentType.Json)); + headers.Add(new(KnownHeaders.Accept, ContentType.Json)); var request = new RestRequest(); @@ -63,7 +63,6 @@ public void AddOrUpdateHeader_ShouldUpdateExistingHeader_WhenHeaderExist() { request.AddOrUpdateHeader(KnownHeaders.Accept, ContentType.Json); // Assert - var headers = GetHeaders(request); GetHeader(request, KnownHeaders.Accept).Should().Be(ContentType.Json); } @@ -121,10 +120,8 @@ public void AddOrUpdateHeaders_ShouldUpdateHeaders_WhenAllExists() { // Assert var requestHeaders = GetHeaders(request); - HeaderParameter[] expected = [ - new HeaderParameter(KnownHeaders.Accept, ContentType.Xml), - new HeaderParameter(KnownHeaders.KeepAlive, "400") - ]; + + HeaderParameter[] expected = [new(KnownHeaders.Accept, ContentType.Xml), new(KnownHeaders.KeepAlive, "400")]; requestHeaders.Should().BeEquivalentTo(expected); } @@ -151,13 +148,33 @@ public void AddOrUpdateHeaders_ShouldAddAndUpdateHeaders_WhenSomeExists() { var requestHeaders = GetHeaders(request); HeaderParameter[] expected = [ - new HeaderParameter(KnownHeaders.Accept, ContentType.Xml), - new HeaderParameter(KnownHeaders.AcceptLanguage, "en-us,en;q=0.5"), - new HeaderParameter(KnownHeaders.KeepAlive, "300") + new(KnownHeaders.Accept, ContentType.Xml), + new(KnownHeaders.AcceptLanguage, "en-us,en;q=0.5"), + new(KnownHeaders.KeepAlive, "300") ]; requestHeaders.Should().BeEquivalentTo(expected); } + [Fact] + public void Should_not_allow_null_header_value() { + string value = null; + var request = new RestRequest(); + // ReSharper disable once AssignNullToNotNullAttribute + Assert.Throws("value", () => request.AddHeader("name", value)); + } + + [Fact] + public void Should_not_allow_null_header_name() { + var request = new RestRequest(); + Assert.Throws("name", () => request.AddHeader(null!, "value")); + } + + [Fact] + public void Should_not_allow_empty_header_name() { + var request = new RestRequest(); + Assert.Throws("name", () => request.AddHeader("", "value")); + } + static Parameter[] GetHeaders(RestRequest request) => request.Parameters.Where(x => x.Type == ParameterType.HttpHeader).ToArray(); static string GetHeader(RestRequest request, string name) => request.Parameters.FirstOrDefault(x => x.Name == name)?.Value?.ToString();