Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/Stripe.net/Infrastructure/Public/ApiRequestorAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ internal static ApiRequestor Adapt(IStripeClient client)
return stripeClient.Requestor;
}

if (client is DefaultStripeClient defaultStripeClient)
{
return defaultStripeClient.Requestor;
}

return new ApiRequestorAdapter(client);
}

Expand Down
80 changes: 80 additions & 0 deletions src/Stripe.net/Infrastructure/Public/DefaultStripeClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
namespace Stripe
{
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// A default implementation of the <see cref="IStripeClient"/> interface. This is used by
/// StripeConfiguration to provide a default client, which is used in conjuction with Service
/// instances when no client is provided.
/// </summary>
internal class DefaultStripeClient : IStripeClient
{
public DefaultStripeClient(string apiKey, string clientId, IHttpClient httpClient)
{
this.Requestor = new LiveApiRequestor(
new StripeClientOptions
{
ApiKey = apiKey,
ClientId = clientId,
HttpClient = httpClient,
}, new List<string>());
}

/// <summary>Gets the base URL for Stripe's API.</summary>
/// <value>The base URL for Stripe's API.</value>
public string ApiBase => this.Requestor?.ApiBase;

/// <summary>Gets the API key used by the client to send requests.</summary>
/// <value>The API key used by the client to send requests.</value>
public string ApiKey => this.Requestor?.ApiKey;

/// <summary>Gets the client ID used by the client in OAuth requests.</summary>
/// <value>The client ID used by the client in OAuth requests.</value>
public string ClientId => this.Requestor?.ClientId;

/// <summary>Gets the base URL for Stripe's OAuth API.</summary>
/// <value>The base URL for Stripe's OAuth API.</value>
public string ConnectBase => this.Requestor?.ConnectBase;

/// <summary>Gets the base URL for Stripe's Files API.</summary>
/// <value>The base URL for Stripe's Files API.</value>
public string FilesBase => this.Requestor?.FilesBase;

/// <summary>Gets the base URL for Stripe's Meter Events API.</summary>
/// <value>The base URL for Stripe's Meter Events API.</value>
public string MeterEventsBase => this.Requestor?.MeterEventsBase;

/// <summary>Gets the <see cref="IHttpClient"/> used to send HTTP requests.</summary>
/// <value>The <see cref="IHttpClient"/> used to send HTTP requests.</value>
public IHttpClient HttpClient => this.Requestor?.HttpClient;

internal ApiRequestor Requestor { get; }

/// <inheritdoc/>
public async Task<T> RequestAsync<T>(
HttpMethod method,
string path,
BaseOptions options,
RequestOptions requestOptions,
CancellationToken cancellationToken = default)
where T : IStripeEntity
{
return await this.Requestor.RequestAsync<T>(BaseAddress.Api, method, path, options, requestOptions, cancellationToken).ConfigureAwait(false);
}

/// <inheritdoc/>
public async Task<Stream> RequestStreamingAsync(
HttpMethod method,
string path,
BaseOptions options,
RequestOptions requestOptions,
CancellationToken cancellationToken = default)
{
return await this.Requestor.RequestStreamingAsync(BaseAddress.Api, method, path, options, requestOptions, cancellationToken).ConfigureAwait(false);
}
}
}
18 changes: 16 additions & 2 deletions src/Stripe.net/Infrastructure/Public/LiveApiRequestor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace Stripe
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
Expand All @@ -17,8 +18,9 @@ internal class LiveApiRequestor : ApiRequestor
internal static readonly List<string> RawRequestUsage = new List<string> { "raw_request" };
private JsonSerializerSettings jsonSerializerSettings;
private StripeClientOptions clientOptions;
private List<string> defaultUsage;

public LiveApiRequestor(StripeClientOptions options)
public LiveApiRequestor(StripeClientOptions options, List<string> defaultUsage = null)
{
// Clone the object passed in, or use an empty option object if it is null
options = options?.Clone() ?? new StripeClientOptions();
Expand All @@ -41,6 +43,7 @@ public LiveApiRequestor(StripeClientOptions options)
this.ConnectBase = options.ConnectBase ?? DefaultConnectBase;
this.FilesBase = options.FilesBase ?? DefaultFilesBase;
this.MeterEventsBase = options.MeterEventsBase ?? DefaultMeterEventsBase;
this.defaultUsage = defaultUsage ?? new List<string>();
this.jsonSerializerSettings = StripeConfiguration.DefaultSerializerSettings(this);
}

Expand Down Expand Up @@ -216,6 +219,17 @@ private StripeRequest MakeStripeRequest(
RequestOptions requestOptions,
ApiMode apiMode)
{
if (this.defaultUsage.Count > 0)
{
var usage = this.defaultUsage;
if (requestOptions?.Usage?.Count > 0)
{
usage = usage.Concat(requestOptions.Usage).ToList();
}

requestOptions = requestOptions.WithUsage(usage);
}

var uri = StripeRequest.BuildUri(
requestOptions?.BaseUrl ?? this.GetBaseUrl(baseAddress),
method,
Expand Down Expand Up @@ -297,7 +311,7 @@ public override async Task<StripeResponse> RawRequestAsync(
throw new InvalidOperationException("content is not allowed for non-POST requests.");
}

requestOptions = requestOptions.WithUsage(RawRequestUsage);
requestOptions = requestOptions.WithUsage(this.defaultUsage.Concat(RawRequestUsage).ToList());
var apiMode = ApiModeUtils.GetApiMode(path);
var uri = StripeRequest.BuildUri(
requestOptions?.BaseUrl ?? this.GetBaseUrl(BaseAddress.Api),
Expand Down
5 changes: 4 additions & 1 deletion src/Stripe.net/Infrastructure/Public/StripeClient.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace Stripe
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading;
Expand All @@ -13,6 +14,8 @@ namespace Stripe
/// </summary>
public class StripeClient : IStripeClient
{
internal static readonly List<string> StripeClientUsage = new List<string> { "stripe_client" };

private JsonSerializerSettings jsonSerializerSettings;

// Fields: The beginning of the section generated from our OpenAPI spec
Expand Down Expand Up @@ -71,7 +74,7 @@ public StripeClient(
}

public StripeClient(StripeClientOptions options)
: this(new LiveApiRequestor(options))
: this(new LiveApiRequestor(options, StripeClientUsage))
{
}

Expand Down
5 changes: 2 additions & 3 deletions src/Stripe.net/Infrastructure/Public/StripeConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ namespace Stripe
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Reflection;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Stripe.Infrastructure;
Expand Down Expand Up @@ -243,7 +242,7 @@ public static void SetApiKey(string newApiKey)
ApiKey = newApiKey;
}

private static StripeClient BuildDefaultStripeClient()
private static IStripeClient BuildDefaultStripeClient()
{
if (ApiKey != null && ApiKey.Length == 0)
{
Expand All @@ -268,7 +267,7 @@ private static StripeClient BuildDefaultStripeClient()
maxNetworkRetries: MaxNetworkRetries,
appInfo: AppInfo,
enableTelemetry: EnableTelemetry);
return new StripeClient(ApiKey, ClientId, httpClient: httpClient);
return new DefaultStripeClient(ApiKey, ClientId, httpClient);
}
}
}
30 changes: 17 additions & 13 deletions src/StripeTests/BaseStripeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,42 +69,46 @@ public BaseStripeTest(
if ((this.StripeMockFixture != null) && (this.MockHttpClientFixture != null))
{
// Set up StripeClient to use stripe-mock with the mock HTTP client
var requestor = this.StripeMockFixture.BuildApiRequestor(this.MockHttpClientFixture.MockHandler.Object);
this.StripeClient = new StripeClient(requestor);
this.Requestor = requestor;
var clientOptions = this.StripeMockFixture.BuildStripeClientOptions(this.MockHttpClientFixture.MockHandler.Object);
var client = new StripeClient(clientOptions);
this.StripeClient = client;
this.Requestor = client.Requestor;

// Reset the mock before each test
this.MockHttpClientFixture.Reset();
}
else if (this.StripeMockFixture != null)
{
// Set up StripeClient to use stripe-mock
var requestor = this.StripeMockFixture.BuildApiRequestor();
this.StripeClient = new StripeClient(requestor);
this.Requestor = requestor;
var clientOptions = this.StripeMockFixture.BuildStripeClientOptions();
var client = new StripeClient(clientOptions);
this.StripeClient = client;
this.Requestor = client.Requestor;
}
else if (this.MockHttpClientFixture != null)
{
// Set up StripeClient with the mock HTTP client
var httpClient = new SystemNetHttpClient(
new HttpClient(this.MockHttpClientFixture.MockHandler.Object));
var requestor = new LiveApiRequestor(new StripeClientOptions
var clientOptions = new StripeClientOptions
{
ApiKey = "sk_test_123",
HttpClient = httpClient,
});
this.StripeClient = new StripeClient(requestor);
this.Requestor = requestor;
};
var client = new StripeClient(clientOptions);
this.StripeClient = client;
this.Requestor = client.Requestor;

// Reset the mock before each test
this.MockHttpClientFixture.Reset();
}
else
{
// Use the default StripeClient
var requestor = new LiveApiRequestor(new StripeClientOptions { ApiKey = "sk_test_123" });
this.StripeClient = new StripeClient(requestor);
this.Requestor = requestor;
var clientOptions = new StripeClientOptions { ApiKey = "sk_test_123" };
var client = new StripeClient(clientOptions);
this.StripeClient = client;
this.Requestor = client.Requestor;
}
}

Expand Down
31 changes: 3 additions & 28 deletions src/StripeTests/Functional/TelemetryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ namespace StripeTests
using Stripe;
using Xunit;

using static TelemetryTestUtils;

public class TelemetryTest : BaseStripeTest
{
public TelemetryTest(MockHttpClientFixture mockHttpClientFixture)
Expand Down Expand Up @@ -95,7 +97,7 @@ public void TelemetryIncludesUsage()
m.Headers,
(_) => true,
(_) => true,
(t) => t != null && t.Count == 2 && t.Contains("llama") && t.Contains("bufo"))),
(t) => t != null && t.Count >= 2 && t.Contains("llama") && t.Contains("bufo"))),
ItExpr.IsAny<CancellationToken>());
}

Expand Down Expand Up @@ -175,33 +177,6 @@ public void NoTelemetryWhenDisabled()
ItExpr.IsAny<CancellationToken>());
}

private static bool TelemetryHeaderMatcher(
HttpHeaders headers,
Func<string, bool> requestIdMatcher,
Func<long, bool> durationMatcher,
Func<List<string>, bool> usageMatcher)
{
if (!headers.Contains("X-Stripe-Client-Telemetry"))
{
return false;
}

var payload = headers.GetValues("X-Stripe-Client-Telemetry").First();

var deserialized = JToken.Parse(payload);
var requestId = (string)deserialized["last_request_metrics"]["request_id"];
var duration = (long)deserialized["last_request_metrics"]["request_duration_ms"];
var usageRaw = deserialized["last_request_metrics"]["usage"];

List<string> usage = null;
if (usageRaw != null)
{
usage = usageRaw.Select(x => (string)x).ToList();
}

return requestIdMatcher(requestId) && durationMatcher(duration) && usageMatcher(usage);
}

private class TestEntity : StripeEntity<TestEntity>
{
}
Expand Down
47 changes: 47 additions & 0 deletions src/StripeTests/Functional/TelemetryTestUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
namespace StripeTests
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Moq;
using Moq.Protected;
using Newtonsoft.Json.Linq;
using Stripe;
using Xunit;

internal class TelemetryTestUtils
{
public static bool TelemetryHeaderMatcher(
HttpHeaders headers,
Func<string, bool> requestIdMatcher,
Func<long, bool> durationMatcher,
Func<List<string>, bool> usageMatcher)
{
if (!headers.Contains("X-Stripe-Client-Telemetry"))
{
return false;
}

var payload = headers.GetValues("X-Stripe-Client-Telemetry").First();

var deserialized = JToken.Parse(payload);
var requestId = (string)deserialized["last_request_metrics"]["request_id"];
var duration = (long)deserialized["last_request_metrics"]["request_duration_ms"];
var usageRaw = deserialized["last_request_metrics"]["usage"];

List<string> usage = null;
if (usageRaw != null)
{
usage = usageRaw.Select(x => (string)x).ToList();
}

return requestIdMatcher(requestId) && durationMatcher(duration) && usageMatcher(usage);
}
}
}
Loading
Loading