From 7f9d4c58f43e9b678732d2810ef6149ec792cd7c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 18:48:04 +0000 Subject: [PATCH 1/5] Initial plan From 567c723d6d696c924aa182c7c8452a1455ecbd6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Nov 2025 18:55:44 +0000 Subject: [PATCH 2/5] Use CultureInfo.InvariantCulture for IFormattable types in AddParameter methods Co-authored-by: alexeyzimarev <2821205+alexeyzimarev@users.noreply.github.com> --- src/RestSharp/Extensions/ObjectExtensions.cs | 31 +++ src/RestSharp/Parameters/ObjectParser.cs | 4 +- src/RestSharp/Parameters/Parameter.cs | 9 +- .../Request/RestRequestExtensions.Query.cs | 4 +- .../Request/RestRequestExtensions.Url.cs | 4 +- .../Request/RestRequestExtensions.cs | 6 +- .../InvariantCultureParameterTests.cs | 177 ++++++++++++++++++ 7 files changed, 226 insertions(+), 9 deletions(-) create mode 100644 src/RestSharp/Extensions/ObjectExtensions.cs create mode 100644 test/RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs diff --git a/src/RestSharp/Extensions/ObjectExtensions.cs b/src/RestSharp/Extensions/ObjectExtensions.cs new file mode 100644 index 000000000..15ca49eea --- /dev/null +++ b/src/RestSharp/Extensions/ObjectExtensions.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation and Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Globalization; + +namespace RestSharp.Extensions; + +static class ObjectExtensions { + /// + /// Converts a value to its string representation using invariant culture for IFormattable types. + /// + /// The type of value to convert + /// The value to convert + /// String representation using invariant culture, or null if value is null + internal static string? ToStringInvariant(this T value) => value switch { + null => null, + IFormattable f => f.ToString(null, CultureInfo.InvariantCulture), + _ => value.ToString() + }; +} diff --git a/src/RestSharp/Parameters/ObjectParser.cs b/src/RestSharp/Parameters/ObjectParser.cs index 3f3464d97..1f0f50850 100644 --- a/src/RestSharp/Parameters/ObjectParser.cs +++ b/src/RestSharp/Parameters/ObjectParser.cs @@ -13,7 +13,9 @@ // limitations under the License. // +using System.Globalization; using System.Reflection; +using RestSharp.Extensions; namespace RestSharp; @@ -72,7 +74,7 @@ IEnumerable GetArray(PropertyInfo propertyInfo, object? value) bool IsAllowedProperty(string propertyName) => includedProperties.Length == 0 || includedProperties.Length > 0 && includedProperties.Contains(propertyName); - string? ParseValue(string? format, object? value) => format == null ? value?.ToString() : string.Format($"{{0:{format}}}", value); + string? ParseValue(string? format, object? value) => format == null ? value.ToStringInvariant() : string.Format(CultureInfo.InvariantCulture, $"{{0:{format}}}", value); } } diff --git a/src/RestSharp/Parameters/Parameter.cs b/src/RestSharp/Parameters/Parameter.cs index b23c592dc..cc6f1434a 100644 --- a/src/RestSharp/Parameters/Parameter.cs +++ b/src/RestSharp/Parameters/Parameter.cs @@ -13,6 +13,7 @@ // limitations under the License. using System.Diagnostics; +using RestSharp.Extensions; namespace RestSharp; @@ -76,10 +77,10 @@ protected Parameter(string? name, object? value, ParameterType type, bool encode 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.QueryString => new QueryParameter(Ensure.NotEmptyString(name, nameof(name)), value?.ToString(), encode), + ParameterType.GetOrPost => new GetOrPostParameter(Ensure.NotEmptyString(name, nameof(name)), value.ToStringInvariant(), encode), + ParameterType.UrlSegment => new UrlSegmentParameter(Ensure.NotEmptyString(name, nameof(name)), value.ToStringInvariant()!, encode), + ParameterType.HttpHeader => new HeaderParameter(name!, value.ToStringInvariant()!), + ParameterType.QueryString => new QueryParameter(Ensure.NotEmptyString(name, nameof(name)), value.ToStringInvariant(), encode), _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) }; diff --git a/src/RestSharp/Request/RestRequestExtensions.Query.cs b/src/RestSharp/Request/RestRequestExtensions.Query.cs index 265dc360d..1ae2f026f 100644 --- a/src/RestSharp/Request/RestRequestExtensions.Query.cs +++ b/src/RestSharp/Request/RestRequestExtensions.Query.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +using RestSharp.Extensions; + namespace RestSharp; public static partial class RestRequestExtensions { @@ -37,6 +39,6 @@ public RestRequest AddQueryParameter(string name, string? value, bool encode = t /// Encode the value or not, default true /// public RestRequest AddQueryParameter(string name, T value, bool encode = true) where T : struct - => request.AddQueryParameter(name, value.ToString(), encode); + => request.AddQueryParameter(name, value.ToStringInvariant(), encode); } } \ No newline at end of file diff --git a/src/RestSharp/Request/RestRequestExtensions.Url.cs b/src/RestSharp/Request/RestRequestExtensions.Url.cs index 499c629df..546cc5c56 100644 --- a/src/RestSharp/Request/RestRequestExtensions.Url.cs +++ b/src/RestSharp/Request/RestRequestExtensions.Url.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +using RestSharp.Extensions; + namespace RestSharp; public static partial class RestRequestExtensions { @@ -37,6 +39,6 @@ public RestRequest AddUrlSegment(string name, string? value, bool encode = true) /// Encode the value or not, default true /// public RestRequest AddUrlSegment(string name, T value, bool encode = true) where T : struct - => request.AddUrlSegment(name, value.ToString(), encode); + => request.AddUrlSegment(name, value.ToStringInvariant(), encode); } } \ No newline at end of file diff --git a/src/RestSharp/Request/RestRequestExtensions.cs b/src/RestSharp/Request/RestRequestExtensions.cs index 82323d07b..be53a9f0c 100644 --- a/src/RestSharp/Request/RestRequestExtensions.cs +++ b/src/RestSharp/Request/RestRequestExtensions.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +using RestSharp.Extensions; + namespace RestSharp; [PublicAPI] @@ -52,7 +54,7 @@ public RestRequest AddParameter(string? name, object value, ParameterType type, /// Encode the value or not, default true /// This request public RestRequest AddParameter(string name, T value, bool encode = true) where T : struct - => request.AddParameter(name, value.ToString(), encode); + => request.AddParameter(name, value.ToStringInvariant(), encode); /// /// Adds or updates a HTTP parameter to the request (QueryString for GET, DELETE, OPTIONS and HEAD; Encoded form for POST and PUT) @@ -72,7 +74,7 @@ public RestRequest AddOrUpdateParameter(string name, string? value, bool encode /// Encode the value or not, default true /// This request public RestRequest AddOrUpdateParameter(string name, T value, bool encode = true) where T : struct - => request.AddOrUpdateParameter(name, value.ToString(), encode); + => request.AddOrUpdateParameter(name, value.ToStringInvariant(), encode); RestRequest AddParameters(IEnumerable parameters) { request.Parameters.AddParameters(parameters); diff --git a/test/RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs b/test/RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs new file mode 100644 index 000000000..f5ec68145 --- /dev/null +++ b/test/RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs @@ -0,0 +1,177 @@ +using System.Globalization; + +namespace RestSharp.Tests.Parameters; + +public class InvariantCultureParameterTests { + [Fact] + public void AddParameter_Double_UsesInvariantCulture() { + // Save original culture + var originalCulture = Thread.CurrentThread.CurrentCulture; + try { + // Set a culture that uses comma as decimal separator + Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); + + var request = new RestRequest(); + request.AddParameter("value", 1.234); + + var parameter = request.Parameters.First(); + parameter.Value.Should().Be("1.234"); + } + finally { + Thread.CurrentThread.CurrentCulture = originalCulture; + } + } + + [Fact] + public void AddOrUpdateParameter_Double_UsesInvariantCulture() { + var originalCulture = Thread.CurrentThread.CurrentCulture; + try { + Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); + + var request = new RestRequest(); + request.AddOrUpdateParameter("value", 1.234); + + var parameter = request.Parameters.First(); + parameter.Value.Should().Be("1.234"); + } + finally { + Thread.CurrentThread.CurrentCulture = originalCulture; + } + } + + [Fact] + public void AddQueryParameter_Double_UsesInvariantCulture() { + var originalCulture = Thread.CurrentThread.CurrentCulture; + try { + Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); + + var request = new RestRequest(); + request.AddQueryParameter("value", 1.234); + + var parameter = request.Parameters.First(); + parameter.Value.Should().Be("1.234"); + } + finally { + Thread.CurrentThread.CurrentCulture = originalCulture; + } + } + + [Fact] + public void AddUrlSegment_Double_UsesInvariantCulture() { + var originalCulture = Thread.CurrentThread.CurrentCulture; + try { + Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); + + var request = new RestRequest("{value}"); + request.AddUrlSegment("value", 1.234); + + var parameter = request.Parameters.First(); + parameter.Value.Should().Be("1.234"); + } + finally { + Thread.CurrentThread.CurrentCulture = originalCulture; + } + } + + [Fact] + public void CreateParameter_Double_UsesInvariantCulture() { + var originalCulture = Thread.CurrentThread.CurrentCulture; + try { + Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); + + var parameter = Parameter.CreateParameter("value", 1.234, ParameterType.GetOrPost); + + parameter.Value.Should().Be("1.234"); + } + finally { + Thread.CurrentThread.CurrentCulture = originalCulture; + } + } + + [Fact] + public void AddParameter_Decimal_UsesInvariantCulture() { + var originalCulture = Thread.CurrentThread.CurrentCulture; + try { + Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE"); + + var request = new RestRequest(); + request.AddParameter("value", 123.456m); + + var parameter = request.Parameters.First(); + parameter.Value.Should().Be("123.456"); + } + finally { + Thread.CurrentThread.CurrentCulture = originalCulture; + } + } + + [Fact] + public void AddParameter_Float_UsesInvariantCulture() { + var originalCulture = Thread.CurrentThread.CurrentCulture; + try { + Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR"); + + var request = new RestRequest(); + request.AddParameter("value", 2.5f); + + var parameter = request.Parameters.First(); + parameter.Value.Should().Be("2.5"); + } + finally { + Thread.CurrentThread.CurrentCulture = originalCulture; + } + } + + [Fact] + public void AddParameter_DateTime_UsesInvariantCulture() { + var originalCulture = Thread.CurrentThread.CurrentCulture; + try { + Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); + + var dateTime = new DateTime(2024, 12, 25, 10, 30, 0, DateTimeKind.Unspecified); + var request = new RestRequest(); + request.AddParameter("date", dateTime); + + var parameter = request.Parameters.First(); + // DateTime.ToString with InvariantCulture uses MM/dd/yyyy format + parameter.Value.Should().Be("12/25/2024 10:30:00"); + } + finally { + Thread.CurrentThread.CurrentCulture = originalCulture; + } + } + + [Fact] + public void AddParameter_Integer_WorksWithAnyCulture() { + var originalCulture = Thread.CurrentThread.CurrentCulture; + try { + Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); + + var request = new RestRequest(); + request.AddParameter("value", 12345); + + var parameter = request.Parameters.First(); + parameter.Value.Should().Be("12345"); + } + finally { + Thread.CurrentThread.CurrentCulture = originalCulture; + } + } + + [Fact] + public void AddObject_WithDoubleProperty_UsesInvariantCulture() { + var originalCulture = Thread.CurrentThread.CurrentCulture; + try { + Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); + + var request = new RestRequest(); + request.AddObject(new { Amount = 1.234 }); + + var parameter = request.Parameters.First(); + parameter.Value.Should().Be("1.234"); + } + finally { + Thread.CurrentThread.CurrentCulture = originalCulture; + } + } +} From 8059e9b8cb1f888213ac10e72b698280868b5ff0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 23:59:48 +0000 Subject: [PATCH 3/5] Make invariant culture formatting opt-in via new method overloads Co-authored-by: alexeyzimarev <2821205+alexeyzimarev@users.noreply.github.com> --- src/RestSharp/Parameters/ObjectParser.cs | 4 +- src/RestSharp/Parameters/Parameter.cs | 9 ++- .../Request/RestRequestExtensions.Query.cs | 15 +++- .../Request/RestRequestExtensions.Url.cs | 15 +++- .../Request/RestRequestExtensions.cs | 28 +++++++- .../InvariantCultureParameterTests.cs | 70 +++++++++---------- 6 files changed, 91 insertions(+), 50 deletions(-) diff --git a/src/RestSharp/Parameters/ObjectParser.cs b/src/RestSharp/Parameters/ObjectParser.cs index 1f0f50850..3f3464d97 100644 --- a/src/RestSharp/Parameters/ObjectParser.cs +++ b/src/RestSharp/Parameters/ObjectParser.cs @@ -13,9 +13,7 @@ // limitations under the License. // -using System.Globalization; using System.Reflection; -using RestSharp.Extensions; namespace RestSharp; @@ -74,7 +72,7 @@ IEnumerable GetArray(PropertyInfo propertyInfo, object? value) bool IsAllowedProperty(string propertyName) => includedProperties.Length == 0 || includedProperties.Length > 0 && includedProperties.Contains(propertyName); - string? ParseValue(string? format, object? value) => format == null ? value.ToStringInvariant() : string.Format(CultureInfo.InvariantCulture, $"{{0:{format}}}", value); + string? ParseValue(string? format, object? value) => format == null ? value?.ToString() : string.Format($"{{0:{format}}}", value); } } diff --git a/src/RestSharp/Parameters/Parameter.cs b/src/RestSharp/Parameters/Parameter.cs index cc6f1434a..b23c592dc 100644 --- a/src/RestSharp/Parameters/Parameter.cs +++ b/src/RestSharp/Parameters/Parameter.cs @@ -13,7 +13,6 @@ // limitations under the License. using System.Diagnostics; -using RestSharp.Extensions; namespace RestSharp; @@ -77,10 +76,10 @@ protected Parameter(string? name, object? value, ParameterType type, bool encode 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.ToStringInvariant(), encode), - ParameterType.UrlSegment => new UrlSegmentParameter(Ensure.NotEmptyString(name, nameof(name)), value.ToStringInvariant()!, encode), - ParameterType.HttpHeader => new HeaderParameter(name!, value.ToStringInvariant()!), - ParameterType.QueryString => new QueryParameter(Ensure.NotEmptyString(name, nameof(name)), value.ToStringInvariant(), encode), + 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.QueryString => new QueryParameter(Ensure.NotEmptyString(name, nameof(name)), value?.ToString(), encode), _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) }; diff --git a/src/RestSharp/Request/RestRequestExtensions.Query.cs b/src/RestSharp/Request/RestRequestExtensions.Query.cs index 1ae2f026f..01697ed53 100644 --- a/src/RestSharp/Request/RestRequestExtensions.Query.cs +++ b/src/RestSharp/Request/RestRequestExtensions.Query.cs @@ -39,6 +39,19 @@ public RestRequest AddQueryParameter(string name, string? value, bool encode = t /// Encode the value or not, default true /// public RestRequest AddQueryParameter(string name, T value, bool encode = true) where T : struct - => request.AddQueryParameter(name, value.ToStringInvariant(), encode); + => request.AddQueryParameter(name, value.ToString(), encode); + + /// + /// Adds a query string parameter to the request. The request resource should not contain any placeholders for this parameter. + /// The parameter will be added to the request URL as a query string using name=value format. + /// The value will be converted to string using invariant culture for IFormattable types. + /// + /// Parameter name + /// Parameter value + /// When true, uses invariant culture for IFormattable types (e.g., numbers, dates) + /// Encode the value or not, default true + /// + public RestRequest AddQueryParameter(string name, T value, bool useInvariantCulture, bool encode = true) where T : struct + => request.AddQueryParameter(name, useInvariantCulture ? value.ToStringInvariant() : value.ToString(), encode); } } \ No newline at end of file diff --git a/src/RestSharp/Request/RestRequestExtensions.Url.cs b/src/RestSharp/Request/RestRequestExtensions.Url.cs index 546cc5c56..1dba74e07 100644 --- a/src/RestSharp/Request/RestRequestExtensions.Url.cs +++ b/src/RestSharp/Request/RestRequestExtensions.Url.cs @@ -39,6 +39,19 @@ public RestRequest AddUrlSegment(string name, string? value, bool encode = true) /// Encode the value or not, default true /// public RestRequest AddUrlSegment(string name, T value, bool encode = true) where T : struct - => request.AddUrlSegment(name, value.ToStringInvariant(), encode); + => request.AddUrlSegment(name, value.ToString(), encode); + + /// + /// Adds a URL segment parameter to the request. The resource URL must have a placeholder for the parameter for it to work. + /// For example, if you add a URL segment parameter with the name "id", the resource URL should contain {id} in its path. + /// The value will be converted to string using invariant culture for IFormattable types. + /// + /// Name of the parameter; must be matching a placeholder in the resource URL as {name} + /// Value of the parameter + /// When true, uses invariant culture for IFormattable types (e.g., numbers, dates) + /// Encode the value or not, default true + /// + public RestRequest AddUrlSegment(string name, T value, bool useInvariantCulture, bool encode = true) where T : struct + => request.AddUrlSegment(name, useInvariantCulture ? value.ToStringInvariant() : value.ToString(), encode); } } \ No newline at end of file diff --git a/src/RestSharp/Request/RestRequestExtensions.cs b/src/RestSharp/Request/RestRequestExtensions.cs index be53a9f0c..a855cba89 100644 --- a/src/RestSharp/Request/RestRequestExtensions.cs +++ b/src/RestSharp/Request/RestRequestExtensions.cs @@ -54,7 +54,19 @@ public RestRequest AddParameter(string? name, object value, ParameterType type, /// Encode the value or not, default true /// This request public RestRequest AddParameter(string name, T value, bool encode = true) where T : struct - => request.AddParameter(name, value.ToStringInvariant(), encode); + => request.AddParameter(name, value.ToString(), encode); + + /// + /// Adds a HTTP parameter to the request (QueryString for GET, DELETE, OPTIONS and HEAD; Encoded form for POST and PUT). + /// The value will be converted to string using invariant culture for IFormattable types. + /// + /// Name of the parameter + /// Value of the parameter + /// When true, uses invariant culture for IFormattable types (e.g., numbers, dates) + /// Encode the value or not, default true + /// This request + public RestRequest AddParameter(string name, T value, bool useInvariantCulture, bool encode = true) where T : struct + => request.AddParameter(name, useInvariantCulture ? value.ToStringInvariant() : value.ToString(), encode); /// /// Adds or updates a HTTP parameter to the request (QueryString for GET, DELETE, OPTIONS and HEAD; Encoded form for POST and PUT) @@ -74,7 +86,19 @@ public RestRequest AddOrUpdateParameter(string name, string? value, bool encode /// Encode the value or not, default true /// This request public RestRequest AddOrUpdateParameter(string name, T value, bool encode = true) where T : struct - => request.AddOrUpdateParameter(name, value.ToStringInvariant(), encode); + => request.AddOrUpdateParameter(name, value.ToString(), encode); + + /// + /// Adds or updates a HTTP parameter to the request (QueryString for GET, DELETE, OPTIONS and HEAD; Encoded form for POST and PUT). + /// The value will be converted to string using invariant culture for IFormattable types. + /// + /// Name of the parameter + /// Value of the parameter + /// When true, uses invariant culture for IFormattable types (e.g., numbers, dates) + /// Encode the value or not, default true + /// This request + public RestRequest AddOrUpdateParameter(string name, T value, bool useInvariantCulture, bool encode = true) where T : struct + => request.AddOrUpdateParameter(name, useInvariantCulture ? value.ToStringInvariant() : value.ToString(), encode); RestRequest AddParameters(IEnumerable parameters) { request.Parameters.AddParameters(parameters); diff --git a/test/RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs b/test/RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs index f5ec68145..d1ca82f90 100644 --- a/test/RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs +++ b/test/RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs @@ -4,7 +4,7 @@ namespace RestSharp.Tests.Parameters; public class InvariantCultureParameterTests { [Fact] - public void AddParameter_Double_UsesInvariantCulture() { + public void AddParameter_Double_UsesInvariantCulture_WhenOptIn() { // Save original culture var originalCulture = Thread.CurrentThread.CurrentCulture; try { @@ -12,7 +12,7 @@ public void AddParameter_Double_UsesInvariantCulture() { Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); var request = new RestRequest(); - request.AddParameter("value", 1.234); + request.AddParameter("value", 1.234, useInvariantCulture: true); var parameter = request.Parameters.First(); parameter.Value.Should().Be("1.234"); @@ -23,16 +23,19 @@ public void AddParameter_Double_UsesInvariantCulture() { } [Fact] - public void AddOrUpdateParameter_Double_UsesInvariantCulture() { + public void AddParameter_Double_UsesCurrentCulture_ByDefault() { + // Save original culture var originalCulture = Thread.CurrentThread.CurrentCulture; try { + // Set a culture that uses comma as decimal separator Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); var request = new RestRequest(); - request.AddOrUpdateParameter("value", 1.234); + request.AddParameter("value", 1.234); var parameter = request.Parameters.First(); - parameter.Value.Should().Be("1.234"); + // Default behavior uses current culture (comma as decimal separator) + parameter.Value.Should().Be("1,234"); } finally { Thread.CurrentThread.CurrentCulture = originalCulture; @@ -40,13 +43,13 @@ public void AddOrUpdateParameter_Double_UsesInvariantCulture() { } [Fact] - public void AddQueryParameter_Double_UsesInvariantCulture() { + public void AddOrUpdateParameter_Double_UsesInvariantCulture_WhenOptIn() { var originalCulture = Thread.CurrentThread.CurrentCulture; try { Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); var request = new RestRequest(); - request.AddQueryParameter("value", 1.234); + request.AddOrUpdateParameter("value", 1.234, useInvariantCulture: true); var parameter = request.Parameters.First(); parameter.Value.Should().Be("1.234"); @@ -57,13 +60,13 @@ public void AddQueryParameter_Double_UsesInvariantCulture() { } [Fact] - public void AddUrlSegment_Double_UsesInvariantCulture() { + public void AddQueryParameter_Double_UsesInvariantCulture_WhenOptIn() { var originalCulture = Thread.CurrentThread.CurrentCulture; try { Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); - var request = new RestRequest("{value}"); - request.AddUrlSegment("value", 1.234); + var request = new RestRequest(); + request.AddQueryParameter("value", 1.234, useInvariantCulture: true); var parameter = request.Parameters.First(); parameter.Value.Should().Be("1.234"); @@ -74,13 +77,15 @@ public void AddUrlSegment_Double_UsesInvariantCulture() { } [Fact] - public void CreateParameter_Double_UsesInvariantCulture() { + public void AddUrlSegment_Double_UsesInvariantCulture_WhenOptIn() { var originalCulture = Thread.CurrentThread.CurrentCulture; try { Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); - var parameter = Parameter.CreateParameter("value", 1.234, ParameterType.GetOrPost); + var request = new RestRequest("{value}"); + request.AddUrlSegment("value", 1.234, useInvariantCulture: true); + var parameter = request.Parameters.First(); parameter.Value.Should().Be("1.234"); } finally { @@ -89,13 +94,13 @@ public void CreateParameter_Double_UsesInvariantCulture() { } [Fact] - public void AddParameter_Decimal_UsesInvariantCulture() { + public void AddParameter_Decimal_UsesInvariantCulture_WhenOptIn() { var originalCulture = Thread.CurrentThread.CurrentCulture; try { Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE"); var request = new RestRequest(); - request.AddParameter("value", 123.456m); + request.AddParameter("value", 123.456m, useInvariantCulture: true); var parameter = request.Parameters.First(); parameter.Value.Should().Be("123.456"); @@ -106,13 +111,13 @@ public void AddParameter_Decimal_UsesInvariantCulture() { } [Fact] - public void AddParameter_Float_UsesInvariantCulture() { + public void AddParameter_Float_UsesInvariantCulture_WhenOptIn() { var originalCulture = Thread.CurrentThread.CurrentCulture; try { Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR"); var request = new RestRequest(); - request.AddParameter("value", 2.5f); + request.AddParameter("value", 2.5f, useInvariantCulture: true); var parameter = request.Parameters.First(); parameter.Value.Should().Be("2.5"); @@ -123,14 +128,14 @@ public void AddParameter_Float_UsesInvariantCulture() { } [Fact] - public void AddParameter_DateTime_UsesInvariantCulture() { + public void AddParameter_DateTime_UsesInvariantCulture_WhenOptIn() { var originalCulture = Thread.CurrentThread.CurrentCulture; try { Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); var dateTime = new DateTime(2024, 12, 25, 10, 30, 0, DateTimeKind.Unspecified); var request = new RestRequest(); - request.AddParameter("date", dateTime); + request.AddParameter("date", dateTime, useInvariantCulture: true); var parameter = request.Parameters.First(); // DateTime.ToString with InvariantCulture uses MM/dd/yyyy format @@ -142,33 +147,22 @@ public void AddParameter_DateTime_UsesInvariantCulture() { } [Fact] - public void AddParameter_Integer_WorksWithAnyCulture() { + public void AddParameter_Integer_SameValueWithOrWithoutInvariantCulture() { var originalCulture = Thread.CurrentThread.CurrentCulture; try { Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); - var request = new RestRequest(); - request.AddParameter("value", 12345); + var requestWithInvariant = new RestRequest(); + requestWithInvariant.AddParameter("value", 12345, useInvariantCulture: true); - var parameter = request.Parameters.First(); - parameter.Value.Should().Be("12345"); - } - finally { - Thread.CurrentThread.CurrentCulture = originalCulture; - } - } - - [Fact] - public void AddObject_WithDoubleProperty_UsesInvariantCulture() { - var originalCulture = Thread.CurrentThread.CurrentCulture; - try { - Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); + var requestWithoutInvariant = new RestRequest(); + requestWithoutInvariant.AddParameter("value", 12345); - var request = new RestRequest(); - request.AddObject(new { Amount = 1.234 }); + var parameterWithInvariant = requestWithInvariant.Parameters.First(); + var parameterWithoutInvariant = requestWithoutInvariant.Parameters.First(); - var parameter = request.Parameters.First(); - parameter.Value.Should().Be("1.234"); + parameterWithInvariant.Value.Should().Be("12345"); + parameterWithoutInvariant.Value.Should().Be("12345"); } finally { Thread.CurrentThread.CurrentCulture = originalCulture; From 45021670d46b0b14ee192e9e343479cebd38c9be Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 07:57:53 +0000 Subject: [PATCH 4/5] Add CultureForParameters option to RestClientOptions for culture-aware parameter formatting Co-authored-by: alexeyzimarev <2821205+alexeyzimarev@users.noreply.github.com> --- src/RestSharp/Extensions/ObjectExtensions.cs | 9 +- src/RestSharp/Options/RestClientOptions.cs | 7 + .../Request/RestRequestExtensions.Culture.cs | 71 ++++++++++ .../Request/RestRequestExtensions.Query.cs | 15 --- .../Request/RestRequestExtensions.Url.cs | 15 --- .../Request/RestRequestExtensions.cs | 26 ---- src/RestSharp/RestClient.Extensions.Params.cs | 41 ++++++ .../InvariantCultureParameterTests.cs | 125 +++++++++++++++--- 8 files changed, 234 insertions(+), 75 deletions(-) create mode 100644 src/RestSharp/Request/RestRequestExtensions.Culture.cs diff --git a/src/RestSharp/Extensions/ObjectExtensions.cs b/src/RestSharp/Extensions/ObjectExtensions.cs index 15ca49eea..5a596d8ba 100644 --- a/src/RestSharp/Extensions/ObjectExtensions.cs +++ b/src/RestSharp/Extensions/ObjectExtensions.cs @@ -18,14 +18,15 @@ namespace RestSharp.Extensions; static class ObjectExtensions { /// - /// Converts a value to its string representation using invariant culture for IFormattable types. + /// Converts a value to its string representation using the specified culture for IFormattable types. /// /// The type of value to convert /// The value to convert - /// String representation using invariant culture, or null if value is null - internal static string? ToStringInvariant(this T value) => value switch { + /// The culture to use for formatting. If null, uses the current culture. + /// String representation using the specified culture, or null if value is null + internal static string? ToStringWithCulture(this T value, CultureInfo? culture) => value switch { null => null, - IFormattable f => f.ToString(null, CultureInfo.InvariantCulture), + IFormattable f => f.ToString(null, culture), _ => value.ToString() }; } diff --git a/src/RestSharp/Options/RestClientOptions.cs b/src/RestSharp/Options/RestClientOptions.cs index d30e34f92..3017fd985 100644 --- a/src/RestSharp/Options/RestClientOptions.cs +++ b/src/RestSharp/Options/RestClientOptions.cs @@ -13,6 +13,7 @@ // limitations under the License. // +using System.Globalization; using System.Net.Http.Headers; using System.Net.Security; using System.Reflection; @@ -230,4 +231,10 @@ public RestClientOptions(string baseUrl) : this(new Uri(Ensure.NotEmptyString(ba /// Custom function to encode a string for use in a URL query. /// public Func EncodeQuery { get; set; } = (s, encoding) => s.UrlEncode(encoding)!; + + /// + /// Culture to use for formatting IFormattable parameter values. Default is null which uses the current culture. + /// Set to to ensure consistent formatting across different locales. + /// + public CultureInfo? CultureForParameters { get; set; } } diff --git a/src/RestSharp/Request/RestRequestExtensions.Culture.cs b/src/RestSharp/Request/RestRequestExtensions.Culture.cs new file mode 100644 index 000000000..335e88aaf --- /dev/null +++ b/src/RestSharp/Request/RestRequestExtensions.Culture.cs @@ -0,0 +1,71 @@ +// Copyright (c) .NET Foundation and Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using RestSharp.Extensions; + +namespace RestSharp; + +public static partial class RestRequestExtensions { + /// + /// Adds a HTTP parameter to the request (QueryString for GET, DELETE, OPTIONS and HEAD; Encoded form for POST and PUT). + /// The value will be converted to string using the culture specified in . + /// + /// The RestClient instance containing the culture settings + /// The request to add the parameter to + /// Name of the parameter + /// Value of the parameter + /// Encode the value or not, default true + /// This request + public static RestRequest AddParameter(this RestRequest request, IRestClient client, string name, T value, bool encode = true) where T : struct + => request.AddParameter(name, value.ToStringWithCulture(client.Options.CultureForParameters), encode); + + /// + /// Adds or updates a HTTP parameter to the request (QueryString for GET, DELETE, OPTIONS and HEAD; Encoded form for POST and PUT). + /// The value will be converted to string using the culture specified in . + /// + /// The RestClient instance containing the culture settings + /// The request to add the parameter to + /// Name of the parameter + /// Value of the parameter + /// Encode the value or not, default true + /// This request + public static RestRequest AddOrUpdateParameter(this RestRequest request, IRestClient client, string name, T value, bool encode = true) where T : struct + => request.AddOrUpdateParameter(name, value.ToStringWithCulture(client.Options.CultureForParameters), encode); + + /// + /// Adds a query string parameter to the request. + /// The value will be converted to string using the culture specified in . + /// + /// The RestClient instance containing the culture settings + /// The request to add the parameter to + /// Parameter name + /// Parameter value + /// Encode the value or not, default true + /// + public static RestRequest AddQueryParameter(this RestRequest request, IRestClient client, string name, T value, bool encode = true) where T : struct + => request.AddQueryParameter(name, value.ToStringWithCulture(client.Options.CultureForParameters), encode); + + /// + /// Adds a URL segment parameter to the request. + /// The value will be converted to string using the culture specified in . + /// + /// The RestClient instance containing the culture settings + /// The request to add the parameter to + /// Name of the parameter; must be matching a placeholder in the resource URL as {name} + /// Value of the parameter + /// Encode the value or not, default true + /// + public static RestRequest AddUrlSegment(this RestRequest request, IRestClient client, string name, T value, bool encode = true) where T : struct + => request.AddUrlSegment(name, value.ToStringWithCulture(client.Options.CultureForParameters), encode); +} diff --git a/src/RestSharp/Request/RestRequestExtensions.Query.cs b/src/RestSharp/Request/RestRequestExtensions.Query.cs index 01697ed53..265dc360d 100644 --- a/src/RestSharp/Request/RestRequestExtensions.Query.cs +++ b/src/RestSharp/Request/RestRequestExtensions.Query.cs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using RestSharp.Extensions; - namespace RestSharp; public static partial class RestRequestExtensions { @@ -40,18 +38,5 @@ public RestRequest AddQueryParameter(string name, string? value, bool encode = t /// public RestRequest AddQueryParameter(string name, T value, bool encode = true) where T : struct => request.AddQueryParameter(name, value.ToString(), encode); - - /// - /// Adds a query string parameter to the request. The request resource should not contain any placeholders for this parameter. - /// The parameter will be added to the request URL as a query string using name=value format. - /// The value will be converted to string using invariant culture for IFormattable types. - /// - /// Parameter name - /// Parameter value - /// When true, uses invariant culture for IFormattable types (e.g., numbers, dates) - /// Encode the value or not, default true - /// - public RestRequest AddQueryParameter(string name, T value, bool useInvariantCulture, bool encode = true) where T : struct - => request.AddQueryParameter(name, useInvariantCulture ? value.ToStringInvariant() : value.ToString(), encode); } } \ No newline at end of file diff --git a/src/RestSharp/Request/RestRequestExtensions.Url.cs b/src/RestSharp/Request/RestRequestExtensions.Url.cs index 1dba74e07..499c629df 100644 --- a/src/RestSharp/Request/RestRequestExtensions.Url.cs +++ b/src/RestSharp/Request/RestRequestExtensions.Url.cs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using RestSharp.Extensions; - namespace RestSharp; public static partial class RestRequestExtensions { @@ -40,18 +38,5 @@ public RestRequest AddUrlSegment(string name, string? value, bool encode = true) /// public RestRequest AddUrlSegment(string name, T value, bool encode = true) where T : struct => request.AddUrlSegment(name, value.ToString(), encode); - - /// - /// Adds a URL segment parameter to the request. The resource URL must have a placeholder for the parameter for it to work. - /// For example, if you add a URL segment parameter with the name "id", the resource URL should contain {id} in its path. - /// The value will be converted to string using invariant culture for IFormattable types. - /// - /// Name of the parameter; must be matching a placeholder in the resource URL as {name} - /// Value of the parameter - /// When true, uses invariant culture for IFormattable types (e.g., numbers, dates) - /// Encode the value or not, default true - /// - public RestRequest AddUrlSegment(string name, T value, bool useInvariantCulture, bool encode = true) where T : struct - => request.AddUrlSegment(name, useInvariantCulture ? value.ToStringInvariant() : value.ToString(), encode); } } \ No newline at end of file diff --git a/src/RestSharp/Request/RestRequestExtensions.cs b/src/RestSharp/Request/RestRequestExtensions.cs index a855cba89..82323d07b 100644 --- a/src/RestSharp/Request/RestRequestExtensions.cs +++ b/src/RestSharp/Request/RestRequestExtensions.cs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using RestSharp.Extensions; - namespace RestSharp; [PublicAPI] @@ -56,18 +54,6 @@ public RestRequest AddParameter(string? name, object value, ParameterType type, public RestRequest AddParameter(string name, T value, bool encode = true) where T : struct => request.AddParameter(name, value.ToString(), encode); - /// - /// Adds a HTTP parameter to the request (QueryString for GET, DELETE, OPTIONS and HEAD; Encoded form for POST and PUT). - /// The value will be converted to string using invariant culture for IFormattable types. - /// - /// Name of the parameter - /// Value of the parameter - /// When true, uses invariant culture for IFormattable types (e.g., numbers, dates) - /// Encode the value or not, default true - /// This request - public RestRequest AddParameter(string name, T value, bool useInvariantCulture, bool encode = true) where T : struct - => request.AddParameter(name, useInvariantCulture ? value.ToStringInvariant() : value.ToString(), encode); - /// /// Adds or updates a HTTP parameter to the request (QueryString for GET, DELETE, OPTIONS and HEAD; Encoded form for POST and PUT) /// @@ -88,18 +74,6 @@ public RestRequest AddOrUpdateParameter(string name, string? value, bool encode public RestRequest AddOrUpdateParameter(string name, T value, bool encode = true) where T : struct => request.AddOrUpdateParameter(name, value.ToString(), encode); - /// - /// Adds or updates a HTTP parameter to the request (QueryString for GET, DELETE, OPTIONS and HEAD; Encoded form for POST and PUT). - /// The value will be converted to string using invariant culture for IFormattable types. - /// - /// Name of the parameter - /// Value of the parameter - /// When true, uses invariant culture for IFormattable types (e.g., numbers, dates) - /// Encode the value or not, default true - /// This request - public RestRequest AddOrUpdateParameter(string name, T value, bool useInvariantCulture, bool encode = true) where T : struct - => request.AddOrUpdateParameter(name, useInvariantCulture ? value.ToStringInvariant() : value.ToString(), encode); - RestRequest AddParameters(IEnumerable parameters) { request.Parameters.AddParameters(parameters); return request; diff --git a/src/RestSharp/RestClient.Extensions.Params.cs b/src/RestSharp/RestClient.Extensions.Params.cs index cd3bb635c..33531d908 100644 --- a/src/RestSharp/RestClient.Extensions.Params.cs +++ b/src/RestSharp/RestClient.Extensions.Params.cs @@ -13,6 +13,8 @@ // limitations under the License. // +using RestSharp.Extensions; + namespace RestSharp; public static partial class RestClientExtensions { @@ -38,6 +40,17 @@ public IRestClient AddDefaultParameter(Parameter parameter) { public IRestClient AddDefaultParameter(string name, string value) => client.AddDefaultParameter(new GetOrPostParameter(name, value)); + /// + /// Adds a default HTTP parameter (QueryString for GET, DELETE, OPTIONS and HEAD; Encoded form for POST and PUT) + /// Used on every request made by this client instance. The value will be formatted using the culture + /// specified in . + /// + /// Name of the parameter + /// Value of the parameter + /// This request + public IRestClient AddDefaultParameter(string name, T value) where T : struct + => client.AddDefaultParameter(new GetOrPostParameter(name, value.ToStringWithCulture(client.Options.CultureForParameters))); + /// /// Adds a default parameter to the client options. There are four types of parameters: /// - GetOrPost: Either a QueryString value or encoded form value based on method @@ -82,6 +95,16 @@ public IRestClient AddDefaultHeaders(Dictionary headers) { public IRestClient AddDefaultUrlSegment(string name, string value) => client.AddDefaultParameter(new UrlSegmentParameter(name, value)); + /// + /// Adds a default URL segment parameter to the RestClient. Used on every request made by this client instance. + /// The value will be formatted using the culture specified in . + /// + /// Name of the segment to add + /// Value of the segment to add + /// + public IRestClient AddDefaultUrlSegment(string name, T value) where T : struct + => client.AddDefaultParameter(new UrlSegmentParameter(name, value.ToStringWithCulture(client.Options.CultureForParameters))); + /// /// Adds a default URL query parameter to the RestClient. Used on every request made by this client instance. /// @@ -90,5 +113,23 @@ public IRestClient AddDefaultUrlSegment(string name, string value) /// public IRestClient AddDefaultQueryParameter(string name, string value) => client.AddDefaultParameter(new QueryParameter(name, value)); + + /// + /// Adds a default URL query parameter to the RestClient. Used on every request made by this client instance. + /// The value will be formatted using the culture specified in . + /// + /// Name of the query parameter to add + /// Value of the query parameter to add + /// + public IRestClient AddDefaultQueryParameter(string name, T value) where T : struct + => client.AddDefaultParameter(new QueryParameter(name, value.ToStringWithCulture(client.Options.CultureForParameters))); + + /// + /// Formats the value using the culture specified in . + /// + /// Value to format + /// String representation of the value using the client's culture setting + public string? FormatValue(T value) where T : struct + => value.ToStringWithCulture(client.Options.CultureForParameters); } } diff --git a/test/RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs b/test/RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs index d1ca82f90..e517f92ac 100644 --- a/test/RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs +++ b/test/RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs @@ -1,18 +1,21 @@ using System.Globalization; +using RichardSzalay.MockHttp; namespace RestSharp.Tests.Parameters; public class InvariantCultureParameterTests { [Fact] - public void AddParameter_Double_UsesInvariantCulture_WhenOptIn() { + public void AddParameter_Double_UsesInvariantCulture_WhenConfigured() { // Save original culture var originalCulture = Thread.CurrentThread.CurrentCulture; try { // Set a culture that uses comma as decimal separator Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); + var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture }; + using var client = new RestClient(options); var request = new RestRequest(); - request.AddParameter("value", 1.234, useInvariantCulture: true); + request.AddParameter(client, "value", 1.234); var parameter = request.Parameters.First(); parameter.Value.Should().Be("1.234"); @@ -43,13 +46,15 @@ public void AddParameter_Double_UsesCurrentCulture_ByDefault() { } [Fact] - public void AddOrUpdateParameter_Double_UsesInvariantCulture_WhenOptIn() { + public void AddOrUpdateParameter_Double_UsesInvariantCulture_WhenConfigured() { var originalCulture = Thread.CurrentThread.CurrentCulture; try { Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); + var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture }; + using var client = new RestClient(options); var request = new RestRequest(); - request.AddOrUpdateParameter("value", 1.234, useInvariantCulture: true); + request.AddOrUpdateParameter(client, "value", 1.234); var parameter = request.Parameters.First(); parameter.Value.Should().Be("1.234"); @@ -60,13 +65,15 @@ public void AddOrUpdateParameter_Double_UsesInvariantCulture_WhenOptIn() { } [Fact] - public void AddQueryParameter_Double_UsesInvariantCulture_WhenOptIn() { + public void AddQueryParameter_Double_UsesInvariantCulture_WhenConfigured() { var originalCulture = Thread.CurrentThread.CurrentCulture; try { Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); + var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture }; + using var client = new RestClient(options); var request = new RestRequest(); - request.AddQueryParameter("value", 1.234, useInvariantCulture: true); + request.AddQueryParameter(client, "value", 1.234); var parameter = request.Parameters.First(); parameter.Value.Should().Be("1.234"); @@ -77,13 +84,15 @@ public void AddQueryParameter_Double_UsesInvariantCulture_WhenOptIn() { } [Fact] - public void AddUrlSegment_Double_UsesInvariantCulture_WhenOptIn() { + public void AddUrlSegment_Double_UsesInvariantCulture_WhenConfigured() { var originalCulture = Thread.CurrentThread.CurrentCulture; try { Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); + var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture }; + using var client = new RestClient(options); var request = new RestRequest("{value}"); - request.AddUrlSegment("value", 1.234, useInvariantCulture: true); + request.AddUrlSegment(client, "value", 1.234); var parameter = request.Parameters.First(); parameter.Value.Should().Be("1.234"); @@ -94,13 +103,87 @@ public void AddUrlSegment_Double_UsesInvariantCulture_WhenOptIn() { } [Fact] - public void AddParameter_Decimal_UsesInvariantCulture_WhenOptIn() { + public void AddDefaultParameter_Double_UsesInvariantCulture_WhenConfigured() { + var originalCulture = Thread.CurrentThread.CurrentCulture; + try { + Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); + + var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture }; + using var client = new RestClient(options); + client.AddDefaultParameter("value", 1.234); + + var parameter = client.DefaultParameters.First(p => p.Name == "value"); + parameter.Value.Should().Be("1.234"); + } + finally { + Thread.CurrentThread.CurrentCulture = originalCulture; + } + } + + [Fact] + public void AddDefaultQueryParameter_Double_UsesInvariantCulture_WhenConfigured() { + var originalCulture = Thread.CurrentThread.CurrentCulture; + try { + Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); + + var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture }; + using var client = new RestClient(options); + client.AddDefaultQueryParameter("value", 1.234); + + var parameter = client.DefaultParameters.First(p => p.Name == "value"); + parameter.Value.Should().Be("1.234"); + } + finally { + Thread.CurrentThread.CurrentCulture = originalCulture; + } + } + + [Fact] + public void AddDefaultUrlSegment_Double_UsesInvariantCulture_WhenConfigured() { + var originalCulture = Thread.CurrentThread.CurrentCulture; + try { + Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); + + var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture }; + using var client = new RestClient(options); + client.AddDefaultUrlSegment("value", 1.234); + + var parameter = client.DefaultParameters.First(p => p.Name == "value"); + parameter.Value.Should().Be("1.234"); + } + finally { + Thread.CurrentThread.CurrentCulture = originalCulture; + } + } + + [Fact] + public void FormatValue_Double_UsesInvariantCulture_WhenConfigured() { + var originalCulture = Thread.CurrentThread.CurrentCulture; + try { + Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); + + var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture }; + using var client = new RestClient(options); + + var formattedValue = client.FormatValue(1.234); + + formattedValue.Should().Be("1.234"); + } + finally { + Thread.CurrentThread.CurrentCulture = originalCulture; + } + } + + [Fact] + public void AddParameter_Decimal_UsesInvariantCulture_WhenConfigured() { var originalCulture = Thread.CurrentThread.CurrentCulture; try { Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE"); + var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture }; + using var client = new RestClient(options); var request = new RestRequest(); - request.AddParameter("value", 123.456m, useInvariantCulture: true); + request.AddParameter(client, "value", 123.456m); var parameter = request.Parameters.First(); parameter.Value.Should().Be("123.456"); @@ -111,13 +194,15 @@ public void AddParameter_Decimal_UsesInvariantCulture_WhenOptIn() { } [Fact] - public void AddParameter_Float_UsesInvariantCulture_WhenOptIn() { + public void AddParameter_Float_UsesInvariantCulture_WhenConfigured() { var originalCulture = Thread.CurrentThread.CurrentCulture; try { Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR"); + var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture }; + using var client = new RestClient(options); var request = new RestRequest(); - request.AddParameter("value", 2.5f, useInvariantCulture: true); + request.AddParameter(client, "value", 2.5f); var parameter = request.Parameters.First(); parameter.Value.Should().Be("2.5"); @@ -128,14 +213,16 @@ public void AddParameter_Float_UsesInvariantCulture_WhenOptIn() { } [Fact] - public void AddParameter_DateTime_UsesInvariantCulture_WhenOptIn() { + public void AddParameter_DateTime_UsesInvariantCulture_WhenConfigured() { var originalCulture = Thread.CurrentThread.CurrentCulture; try { Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); var dateTime = new DateTime(2024, 12, 25, 10, 30, 0, DateTimeKind.Unspecified); + var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture }; + using var client = new RestClient(options); var request = new RestRequest(); - request.AddParameter("date", dateTime, useInvariantCulture: true); + request.AddParameter(client, "date", dateTime); var parameter = request.Parameters.First(); // DateTime.ToString with InvariantCulture uses MM/dd/yyyy format @@ -152,8 +239,10 @@ public void AddParameter_Integer_SameValueWithOrWithoutInvariantCulture() { try { Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK"); + var options = new RestClientOptions { CultureForParameters = CultureInfo.InvariantCulture }; + using var client = new RestClient(options); var requestWithInvariant = new RestRequest(); - requestWithInvariant.AddParameter("value", 12345, useInvariantCulture: true); + requestWithInvariant.AddParameter(client, "value", 12345); var requestWithoutInvariant = new RestRequest(); requestWithoutInvariant.AddParameter("value", 12345); @@ -168,4 +257,10 @@ public void AddParameter_Integer_SameValueWithOrWithoutInvariantCulture() { Thread.CurrentThread.CurrentCulture = originalCulture; } } + + [Fact] + public void CultureForParameters_DefaultValue_IsNull() { + var options = new RestClientOptions(); + options.CultureForParameters.Should().BeNull(); + } } From 1fb26deb968574ac7e268a9426e73238933df914 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 08:02:03 +0000 Subject: [PATCH 5/5] Remove unused import in test file Co-authored-by: alexeyzimarev <2821205+alexeyzimarev@users.noreply.github.com> --- .../RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs b/test/RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs index e517f92ac..43d5ccf50 100644 --- a/test/RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs +++ b/test/RestSharp.Tests/Parameters/InvariantCultureParameterTests.cs @@ -1,5 +1,4 @@ using System.Globalization; -using RichardSzalay.MockHttp; namespace RestSharp.Tests.Parameters;