Skip to content

Commit bac1fa7

Browse files
authored
Separates default StripeConfiguration.StripeClient from feature-full StripeClient class and add telemetry string to StripeClient (#3150)
1 parent 9916ba7 commit bac1fa7

File tree

11 files changed

+268
-51
lines changed

11 files changed

+268
-51
lines changed

src/Stripe.net/Infrastructure/Public/ApiRequestorAdapter.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ internal static ApiRequestor Adapt(IStripeClient client)
3535
return stripeClient.Requestor;
3636
}
3737

38+
if (client is DefaultStripeClient defaultStripeClient)
39+
{
40+
return defaultStripeClient.Requestor;
41+
}
42+
3843
return new ApiRequestorAdapter(client);
3944
}
4045

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
namespace Stripe
2+
{
3+
using System.Collections.Generic;
4+
using System.IO;
5+
using System.Net.Http;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
9+
/// <summary>
10+
/// A default implementation of the <see cref="IStripeClient"/> interface. This is used by
11+
/// StripeConfiguration to provide a default client, which is used in conjuction with Service
12+
/// instances when no client is provided.
13+
/// </summary>
14+
internal class DefaultStripeClient : IStripeClient
15+
{
16+
public DefaultStripeClient(string apiKey, string clientId, IHttpClient httpClient)
17+
{
18+
this.Requestor = new LiveApiRequestor(
19+
new StripeClientOptions
20+
{
21+
ApiKey = apiKey,
22+
ClientId = clientId,
23+
HttpClient = httpClient,
24+
}, new List<string>());
25+
}
26+
27+
/// <summary>Gets the base URL for Stripe's API.</summary>
28+
/// <value>The base URL for Stripe's API.</value>
29+
public string ApiBase => this.Requestor?.ApiBase;
30+
31+
/// <summary>Gets the API key used by the client to send requests.</summary>
32+
/// <value>The API key used by the client to send requests.</value>
33+
public string ApiKey => this.Requestor?.ApiKey;
34+
35+
/// <summary>Gets the client ID used by the client in OAuth requests.</summary>
36+
/// <value>The client ID used by the client in OAuth requests.</value>
37+
public string ClientId => this.Requestor?.ClientId;
38+
39+
/// <summary>Gets the base URL for Stripe's OAuth API.</summary>
40+
/// <value>The base URL for Stripe's OAuth API.</value>
41+
public string ConnectBase => this.Requestor?.ConnectBase;
42+
43+
/// <summary>Gets the base URL for Stripe's Files API.</summary>
44+
/// <value>The base URL for Stripe's Files API.</value>
45+
public string FilesBase => this.Requestor?.FilesBase;
46+
47+
/// <summary>Gets the base URL for Stripe's Meter Events API.</summary>
48+
/// <value>The base URL for Stripe's Meter Events API.</value>
49+
public string MeterEventsBase => this.Requestor?.MeterEventsBase;
50+
51+
/// <summary>Gets the <see cref="IHttpClient"/> used to send HTTP requests.</summary>
52+
/// <value>The <see cref="IHttpClient"/> used to send HTTP requests.</value>
53+
public IHttpClient HttpClient => this.Requestor?.HttpClient;
54+
55+
internal ApiRequestor Requestor { get; }
56+
57+
/// <inheritdoc/>
58+
public async Task<T> RequestAsync<T>(
59+
HttpMethod method,
60+
string path,
61+
BaseOptions options,
62+
RequestOptions requestOptions,
63+
CancellationToken cancellationToken = default)
64+
where T : IStripeEntity
65+
{
66+
return await this.Requestor.RequestAsync<T>(BaseAddress.Api, method, path, options, requestOptions, cancellationToken).ConfigureAwait(false);
67+
}
68+
69+
/// <inheritdoc/>
70+
public async Task<Stream> RequestStreamingAsync(
71+
HttpMethod method,
72+
string path,
73+
BaseOptions options,
74+
RequestOptions requestOptions,
75+
CancellationToken cancellationToken = default)
76+
{
77+
return await this.Requestor.RequestStreamingAsync(BaseAddress.Api, method, path, options, requestOptions, cancellationToken).ConfigureAwait(false);
78+
}
79+
}
80+
}

src/Stripe.net/Infrastructure/Public/LiveApiRequestor.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace Stripe
33
using System;
44
using System.Collections.Generic;
55
using System.IO;
6+
using System.Linq;
67
using System.Net;
78
using System.Net.Http;
89
using System.Threading;
@@ -17,8 +18,9 @@ internal class LiveApiRequestor : ApiRequestor
1718
internal static readonly List<string> RawRequestUsage = new List<string> { "raw_request" };
1819
private JsonSerializerSettings jsonSerializerSettings;
1920
private StripeClientOptions clientOptions;
21+
private List<string> defaultUsage;
2022

21-
public LiveApiRequestor(StripeClientOptions options)
23+
public LiveApiRequestor(StripeClientOptions options, List<string> defaultUsage = null)
2224
{
2325
// Clone the object passed in, or use an empty option object if it is null
2426
options = options?.Clone() ?? new StripeClientOptions();
@@ -41,6 +43,7 @@ public LiveApiRequestor(StripeClientOptions options)
4143
this.ConnectBase = options.ConnectBase ?? DefaultConnectBase;
4244
this.FilesBase = options.FilesBase ?? DefaultFilesBase;
4345
this.MeterEventsBase = options.MeterEventsBase ?? DefaultMeterEventsBase;
46+
this.defaultUsage = defaultUsage ?? new List<string>();
4447
this.jsonSerializerSettings = StripeConfiguration.DefaultSerializerSettings(this);
4548
}
4649

@@ -216,6 +219,17 @@ private StripeRequest MakeStripeRequest(
216219
RequestOptions requestOptions,
217220
ApiMode apiMode)
218221
{
222+
if (this.defaultUsage.Count > 0)
223+
{
224+
var usage = this.defaultUsage;
225+
if (requestOptions?.Usage?.Count > 0)
226+
{
227+
usage = usage.Concat(requestOptions.Usage).ToList();
228+
}
229+
230+
requestOptions = requestOptions.WithUsage(usage);
231+
}
232+
219233
var uri = StripeRequest.BuildUri(
220234
requestOptions?.BaseUrl ?? this.GetBaseUrl(baseAddress),
221235
method,
@@ -297,7 +311,7 @@ public override async Task<StripeResponse> RawRequestAsync(
297311
throw new InvalidOperationException("content is not allowed for non-POST requests.");
298312
}
299313

300-
requestOptions = requestOptions.WithUsage(RawRequestUsage);
314+
requestOptions = requestOptions.WithUsage(this.defaultUsage.Concat(RawRequestUsage).ToList());
301315
var apiMode = ApiModeUtils.GetApiMode(path);
302316
var uri = StripeRequest.BuildUri(
303317
requestOptions?.BaseUrl ?? this.GetBaseUrl(BaseAddress.Api),

src/Stripe.net/Infrastructure/Public/StripeClient.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace Stripe
22
{
33
using System;
4+
using System.Collections.Generic;
45
using System.IO;
56
using System.Net.Http;
67
using System.Threading;
@@ -13,6 +14,8 @@ namespace Stripe
1314
/// </summary>
1415
public class StripeClient : IStripeClient
1516
{
17+
internal static readonly List<string> StripeClientUsage = new List<string> { "stripe_client" };
18+
1619
private JsonSerializerSettings jsonSerializerSettings;
1720

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

7376
public StripeClient(StripeClientOptions options)
74-
: this(new LiveApiRequestor(options))
77+
: this(new LiveApiRequestor(options, StripeClientUsage))
7578
{
7679
}
7780

src/Stripe.net/Infrastructure/Public/StripeConfiguration.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ namespace Stripe
33
using System;
44
using System.Collections.Generic;
55
using System.Configuration;
6-
using System.Reflection;
76
using System.Runtime.Serialization;
87
using Newtonsoft.Json;
98
using Stripe.Infrastructure;
@@ -243,7 +242,7 @@ public static void SetApiKey(string newApiKey)
243242
ApiKey = newApiKey;
244243
}
245244

246-
private static StripeClient BuildDefaultStripeClient()
245+
private static IStripeClient BuildDefaultStripeClient()
247246
{
248247
if (ApiKey != null && ApiKey.Length == 0)
249248
{
@@ -268,7 +267,7 @@ private static StripeClient BuildDefaultStripeClient()
268267
maxNetworkRetries: MaxNetworkRetries,
269268
appInfo: AppInfo,
270269
enableTelemetry: EnableTelemetry);
271-
return new StripeClient(ApiKey, ClientId, httpClient: httpClient);
270+
return new DefaultStripeClient(ApiKey, ClientId, httpClient);
272271
}
273272
}
274273
}

src/StripeTests/BaseStripeTest.cs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -69,42 +69,46 @@ public BaseStripeTest(
6969
if ((this.StripeMockFixture != null) && (this.MockHttpClientFixture != null))
7070
{
7171
// Set up StripeClient to use stripe-mock with the mock HTTP client
72-
var requestor = this.StripeMockFixture.BuildApiRequestor(this.MockHttpClientFixture.MockHandler.Object);
73-
this.StripeClient = new StripeClient(requestor);
74-
this.Requestor = requestor;
72+
var clientOptions = this.StripeMockFixture.BuildStripeClientOptions(this.MockHttpClientFixture.MockHandler.Object);
73+
var client = new StripeClient(clientOptions);
74+
this.StripeClient = client;
75+
this.Requestor = client.Requestor;
7576

7677
// Reset the mock before each test
7778
this.MockHttpClientFixture.Reset();
7879
}
7980
else if (this.StripeMockFixture != null)
8081
{
8182
// Set up StripeClient to use stripe-mock
82-
var requestor = this.StripeMockFixture.BuildApiRequestor();
83-
this.StripeClient = new StripeClient(requestor);
84-
this.Requestor = requestor;
83+
var clientOptions = this.StripeMockFixture.BuildStripeClientOptions();
84+
var client = new StripeClient(clientOptions);
85+
this.StripeClient = client;
86+
this.Requestor = client.Requestor;
8587
}
8688
else if (this.MockHttpClientFixture != null)
8789
{
8890
// Set up StripeClient with the mock HTTP client
8991
var httpClient = new SystemNetHttpClient(
9092
new HttpClient(this.MockHttpClientFixture.MockHandler.Object));
91-
var requestor = new LiveApiRequestor(new StripeClientOptions
93+
var clientOptions = new StripeClientOptions
9294
{
9395
ApiKey = "sk_test_123",
9496
HttpClient = httpClient,
95-
});
96-
this.StripeClient = new StripeClient(requestor);
97-
this.Requestor = requestor;
97+
};
98+
var client = new StripeClient(clientOptions);
99+
this.StripeClient = client;
100+
this.Requestor = client.Requestor;
98101

99102
// Reset the mock before each test
100103
this.MockHttpClientFixture.Reset();
101104
}
102105
else
103106
{
104107
// Use the default StripeClient
105-
var requestor = new LiveApiRequestor(new StripeClientOptions { ApiKey = "sk_test_123" });
106-
this.StripeClient = new StripeClient(requestor);
107-
this.Requestor = requestor;
108+
var clientOptions = new StripeClientOptions { ApiKey = "sk_test_123" };
109+
var client = new StripeClient(clientOptions);
110+
this.StripeClient = client;
111+
this.Requestor = client.Requestor;
108112
}
109113
}
110114

src/StripeTests/Functional/TelemetryTest.cs

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ namespace StripeTests
1515
using Stripe;
1616
using Xunit;
1717

18+
using static TelemetryTestUtils;
19+
1820
public class TelemetryTest : BaseStripeTest
1921
{
2022
public TelemetryTest(MockHttpClientFixture mockHttpClientFixture)
@@ -95,7 +97,7 @@ public void TelemetryIncludesUsage()
9597
m.Headers,
9698
(_) => true,
9799
(_) => true,
98-
(t) => t != null && t.Count == 2 && t.Contains("llama") && t.Contains("bufo"))),
100+
(t) => t != null && t.Count >= 2 && t.Contains("llama") && t.Contains("bufo"))),
99101
ItExpr.IsAny<CancellationToken>());
100102
}
101103

@@ -175,33 +177,6 @@ public void NoTelemetryWhenDisabled()
175177
ItExpr.IsAny<CancellationToken>());
176178
}
177179

178-
private static bool TelemetryHeaderMatcher(
179-
HttpHeaders headers,
180-
Func<string, bool> requestIdMatcher,
181-
Func<long, bool> durationMatcher,
182-
Func<List<string>, bool> usageMatcher)
183-
{
184-
if (!headers.Contains("X-Stripe-Client-Telemetry"))
185-
{
186-
return false;
187-
}
188-
189-
var payload = headers.GetValues("X-Stripe-Client-Telemetry").First();
190-
191-
var deserialized = JToken.Parse(payload);
192-
var requestId = (string)deserialized["last_request_metrics"]["request_id"];
193-
var duration = (long)deserialized["last_request_metrics"]["request_duration_ms"];
194-
var usageRaw = deserialized["last_request_metrics"]["usage"];
195-
196-
List<string> usage = null;
197-
if (usageRaw != null)
198-
{
199-
usage = usageRaw.Select(x => (string)x).ToList();
200-
}
201-
202-
return requestIdMatcher(requestId) && durationMatcher(duration) && usageMatcher(usage);
203-
}
204-
205180
private class TestEntity : StripeEntity<TestEntity>
206181
{
207182
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
namespace StripeTests
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Net;
7+
using System.Net.Http;
8+
using System.Net.Http.Headers;
9+
using System.Text;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
using Moq;
13+
using Moq.Protected;
14+
using Newtonsoft.Json.Linq;
15+
using Stripe;
16+
using Xunit;
17+
18+
internal class TelemetryTestUtils
19+
{
20+
public static bool TelemetryHeaderMatcher(
21+
HttpHeaders headers,
22+
Func<string, bool> requestIdMatcher,
23+
Func<long, bool> durationMatcher,
24+
Func<List<string>, bool> usageMatcher)
25+
{
26+
if (!headers.Contains("X-Stripe-Client-Telemetry"))
27+
{
28+
return false;
29+
}
30+
31+
var payload = headers.GetValues("X-Stripe-Client-Telemetry").First();
32+
33+
var deserialized = JToken.Parse(payload);
34+
var requestId = (string)deserialized["last_request_metrics"]["request_id"];
35+
var duration = (long)deserialized["last_request_metrics"]["request_duration_ms"];
36+
var usageRaw = deserialized["last_request_metrics"]["usage"];
37+
38+
List<string> usage = null;
39+
if (usageRaw != null)
40+
{
41+
usage = usageRaw.Select(x => (string)x).ToList();
42+
}
43+
44+
return requestIdMatcher(requestId) && durationMatcher(duration) && usageMatcher(usage);
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)