Skip to content

Commit

Permalink
Merge branch 'refs/heads/dev' into wrapping-up-interceptors
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/Directory.Build.props
#	src/RestSharp/RestClient.cs
  • Loading branch information
alexeyzimarev committed Apr 2, 2024
2 parents 95ed004 + 1c86286 commit f6be96c
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 36 deletions.
12 changes: 6 additions & 6 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ Now, we need to implement the `GetToken` function in the class:

```csharp
async Task<string> GetToken() {
var options = new RestClientOptions(_baseUrl);
using var client = new RestClient(options) {
var options = new RestClientOptions(_baseUrl){
Authenticator = new HttpBasicAuthenticator(_clientId, _clientSecret),
};
using var client = new RestClient(options);

var request = new RestRequest("oauth2/token")
.AddParameter("grant_type", "client_credentials");
Expand Down Expand Up @@ -107,11 +107,11 @@ public class TwitterClient : ITwitterClient, IDisposable {
readonly RestClient _client;

public TwitterClient(string apiKey, string apiKeySecret) {
var options = new RestClientOptions("https://api.twitter.com/2");

_client = new RestClient(options) {
var options = new RestClientOptions("https://api.twitter.com/2"){
Authenticator = new TwitterAuthenticator("https://api.twitter.com", apiKey, apiKeySecret)
};

_client = new RestClient(options);
}

public async Task<TwitterUser> GetUser(string user) {
Expand Down Expand Up @@ -594,7 +594,7 @@ One way of doing it is to use `RestClient` constructors that accept an instance

- `BaseAddress` will be used to set the base address of the `HttpClient` instance if base address is not set there already.
- `MaxTimeout`
- `UserAgent` will be set if the `User-Agent` header is not set on the `HttpClient` instance already.
- `UserAgent` will be added to the `RestClient.DefaultParameters` list as a HTTP header. This will be added to each request made by the `RestClient`, and the `HttpClient` instance will not be modified. This is to allow the `HttpClient` instance to be reused for scenarios where different `User-Agent` headers are required.
- `Expect100Continue`

Another option is to use a simple HTTP client factory as described [above](#simple-factory).
Expand Down
1 change: 1 addition & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/RestSharp/Authenticators/OAuth/OAuthTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ static class OAuthTools {
internal static IEnumerable<string> SortParametersExcludingSignature(WebPairCollection parameters)
=> parameters
.Where(x => !x.Name.EqualsIgnoreCase("oauth_signature"))
.Select(x => new WebPair(UrlEncodeStrict(x.Name), UrlEncodeStrict(x.Value)))
.Select(x => new WebPair(UrlEncodeStrict(x.Name), UrlEncodeRelaxed(x.Value)))
.OrderBy(x => x, WebPair.Comparer)
.Select(x => x.GetQueryParameter(false));

Expand Down
4 changes: 2 additions & 2 deletions src/RestSharp/KnownHeaders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public static class KnownHeaders {
LastModified
};

static readonly HashSet<string> ContentHeadersHash = new(ContentHeaders.Select(x => x.ToLower()));
static readonly HashSet<string> ContentHeadersHash = new(ContentHeaders, StringComparer.InvariantCultureIgnoreCase);

internal static bool IsContentHeader(string key) => ContentHeadersHash.Contains(key.ToLower());
internal static bool IsContentHeader(string key) => ContentHeadersHash.Contains(key);
}
18 changes: 12 additions & 6 deletions src/RestSharp/RestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public partial class RestClient : IRestClient {

ConfigureSerializers(configureSerialization);
Options = new ReadOnlyRestClientOptions(options);
DefaultParameters = new DefaultParameters(Options);

if (useClientFactory) {
_disposeHttpClient = false;
Expand All @@ -89,7 +90,6 @@ public partial class RestClient : IRestClient {
HttpClient = GetClient();
}

DefaultParameters = new DefaultParameters(Options);
return;

HttpClient GetClient() {
Expand All @@ -98,6 +98,7 @@ public partial class RestClient : IRestClient {
var finalHandler = options.ConfigureMessageHandler?.Invoke(handler) ?? handler;
var httpClient = new HttpClient(finalHandler);
ConfigureHttpClient(httpClient, options);
ConfigureDefaultParameters(options);
configureDefaultHeaders?.Invoke(httpClient.DefaultRequestHeaders);
return httpClient;
}
Expand Down Expand Up @@ -189,6 +190,7 @@ public partial class RestClient : IRestClient {

if (options != null) {
ConfigureHttpClient(httpClient, options);
ConfigureDefaultParameters(options);
}
}

Expand Down Expand Up @@ -226,11 +228,6 @@ public partial class RestClient : IRestClient {
static void ConfigureHttpClient(HttpClient httpClient, RestClientOptions options) {
if (options.MaxTimeout > 0) httpClient.Timeout = TimeSpan.FromMilliseconds(options.MaxTimeout);

if (options.UserAgent != null &&
httpClient.DefaultRequestHeaders.UserAgent.All(x => $"{x.Product?.Name}/{x.Product?.Version}" != options.UserAgent)) {
httpClient.DefaultRequestHeaders.TryAddWithoutValidation(KnownHeaders.UserAgent, options.UserAgent);
}

if (options.Expect100Continue != null) httpClient.DefaultRequestHeaders.ExpectContinue = options.Expect100Continue;
}

Expand Down Expand Up @@ -278,6 +275,15 @@ public partial class RestClient : IRestClient {
AcceptedContentTypes = Serializers.GetAcceptedContentTypes();
}

void ConfigureDefaultParameters(RestClientOptions options) {
if (options.UserAgent != null) {
if (!options.AllowMultipleDefaultParametersWithSameName
&& DefaultParameters.Any(parameter => parameter.Type == ParameterType.HttpHeader && parameter.Name == KnownHeaders.UserAgent))
DefaultParameters.RemoveParameter(KnownHeaders.UserAgent, ParameterType.HttpHeader);
DefaultParameters.AddParameter(Parameter.CreateParameter(KnownHeaders.UserAgent, options.UserAgent, ParameterType.HttpHeader));
}
}

readonly bool _disposeHttpClient;
bool _disposed;

Expand Down
5 changes: 4 additions & 1 deletion src/RestSharp/RestSharp.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
Expand Down Expand Up @@ -56,4 +56,7 @@
<ItemGroup>
<ProjectReference Include="$(RepoRoot)\gen\SourceGenerator\SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup>
<ItemGroup>
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>
</Project>
19 changes: 15 additions & 4 deletions src/RestSharp/Serializers/RestSerializers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,24 @@ public IRestSerializer GetSerializer(DataFormat dataFormat)
if (string.IsNullOrWhiteSpace(response.Content)) return null;

var contentType = response.ContentType ?? DetectContentType()?.Value;
if (contentType == null) return null;

if (contentType == null) {
Serializers.TryGetValue(response.Request.RequestFormat, out var serializerByRequestFormat);
return serializerByRequestFormat?.GetSerializer().Deserializer;
}

var serializer = Serializers.Values.FirstOrDefault(x => x.SupportsContentType(contentType));

var factory = serializer ??
(Serializers.ContainsKey(response.Request.RequestFormat) ? Serializers[response.Request.RequestFormat] : null);
return factory?.GetSerializer().Deserializer;
if (serializer == null) {
var detectedType = DetectContentType()?.Value;

if (detectedType != null && detectedType != contentType)
{
serializer = Serializers.Values.FirstOrDefault(x => x.SupportsContentType(detectedType));
}
}

return serializer?.GetSerializer().Deserializer;

ContentType? DetectContentType()
=> response.Content![0] switch {
Expand Down
26 changes: 19 additions & 7 deletions test/RestSharp.Tests.Integrated/OAuth1Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,29 @@ class QueueItem {
actual.Should().BeEquivalentTo(expected);
}

[Fact]
public void Properly_Encodes_Parameter_Names() {
var postData = new WebPairCollection {
{ "name[first]", "Chuck" },
{ "name[last]", "Testa" }
};
[Theory]
[MemberData(nameof(EncodeParametersTestData))]
public void Properly_Encodes_Parameter_Names(IList<(string, string)> parameters, string expected) {
var postData = new WebPairCollection();
postData.AddRange(parameters.Select(x => new WebPair(x.Item1, x.Item2)));
var sortedParams = OAuthTools.SortParametersExcludingSignature(postData);

sortedParams.First().Should().Be("name%5Bfirst%5D=Chuck");
sortedParams.First().Should().Be(expected);
}

public static IEnumerable<object[]> EncodeParametersTestData =>
new List<object[]>
{
new object[] {
new List<(string, string)> { ("name[first]", "Chuck"), ("name[last]", "Testa") },
"name%5Bfirst%5D=Chuck"
},
new object[] {
new List<(string, string)> { ("country", "España") },
"country=Espa%C3%B1a"
}
};

[Fact]
public void Use_RFC_3986_Encoding_For_Auth_Signature_Base() {
// reserved characters for 2396 and 3986
Expand Down
39 changes: 30 additions & 9 deletions test/RestSharp.Tests/RestClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,20 +118,41 @@ public class RestClientTests {
}

[Fact]
public void ConfigureHttpClient_does_not_duplicate_user_agent_for_same_client() {
public void ConfigureDefaultParameters_sets_user_agent_new_httpClient_instance() {
// arrange
var clientOptions = new RestClientOptions();

// act
var restClient = new RestClient(clientOptions);

//assert
Assert.Single(
restClient.DefaultParameters,
parameter => parameter.Type == ParameterType.HttpHeader &&
parameter.Name == KnownHeaders.UserAgent &&
parameter.Value is string valueAsString &&
valueAsString == clientOptions.UserAgent);

Assert.Empty(restClient.HttpClient.DefaultRequestHeaders.UserAgent);
}

[Fact]
public void ConfigureDefaultParameters_sets_user_agent_given_httpClient_instance() {
// arrange
var httpClient = new HttpClient();
var clientOptions = new RestClientOptions();

// act
var unused = new RestClient(httpClient, clientOptions);
var dummy = new RestClient(httpClient, clientOptions);
var restClient = new RestClient(httpClient, clientOptions);

// assert
Assert.Contains(
httpClient.DefaultRequestHeaders.UserAgent,
agent => $"{agent.Product.Name}/{agent.Product.Version}" == clientOptions.UserAgent
);
Assert.Single(httpClient.DefaultRequestHeaders.UserAgent);
//assert
Assert.Single(
restClient.DefaultParameters,
parameter => parameter.Type == ParameterType.HttpHeader &&
parameter.Name == KnownHeaders.UserAgent &&
parameter.Value is string valueAsString &&
valueAsString == clientOptions.UserAgent);

Assert.Empty(httpClient.DefaultRequestHeaders.UserAgent);
}
}

0 comments on commit f6be96c

Please sign in to comment.