Skip to content

Commit ef93cb5

Browse files
Support native AoT
- Add support for native AoT for `net6.0` and `net7.0`. - Use `JsonSerializer.SerializeToUtf8Bytes()` where possible. - Fix `ArgumentException.ParamName` not being asserted on.
1 parent 91d1f65 commit ef93cb5

21 files changed

+390
-120
lines changed

Directory.Build.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
4242
<UseArtifactsOutput>true</UseArtifactsOutput>
4343
<AssemblyVersion>4.0.0.0</AssemblyVersion>
44-
<VersionPrefix>4.1.0</VersionPrefix>
44+
<VersionPrefix>4.2.0</VersionPrefix>
4545
<VersionSuffix Condition=" '$(VersionSuffix)' == '' AND '$(GITHUB_ACTIONS)' != '' AND '$(GITHUB_HEAD_REF)' != '' ">beta.$(GITHUB_RUN_NUMBER)</VersionSuffix>
4646
<VersionPrefix Condition=" $(GITHUB_REF.StartsWith(`refs/tags/v`)) ">$(GITHUB_REF.Replace('refs/tags/v', ''))</VersionPrefix>
4747
<VersionSuffix Condition=" $(GITHUB_REF.StartsWith(`refs/tags/v`)) "></VersionSuffix>
@@ -67,6 +67,7 @@
6767
<CollectCoverage>true</CollectCoverage>
6868
<CoverletOutputFormat>cobertura,json</CoverletOutputFormat>
6969
<Exclude>[*.Benchmarks]*,[*.Tests]*,[Refit]*,[SampleApp]*,[xunit.*]*</Exclude>
70+
<ExcludeByAttribute>GeneratedCodeAttribute</ExcludeByAttribute>
7071
<Threshold>95</Threshold>
7172
</PropertyGroup>
7273
</Project>

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ using var client = options.CreateHttpClient();
5757
// The value of json will be: {"Id":1, "Link":"https://www.just-eat.co.uk/privacy-policy"}
5858
string json = await client.GetStringAsync("https://public.je-apis.com/terms");
5959
```
60-
<sup><a href='/tests/HttpClientInterception.Tests/Examples.cs#L44-L66' title='Snippet source file'>snippet source</a> | <a href='#snippet-minimal-example' title='Start of snippet'>anchor</a></sup>
60+
<sup><a href='/tests/HttpClientInterception.Tests/Examples.cs#L46-L68' title='Snippet source file'>snippet source</a> | <a href='#snippet-minimal-example' title='Start of snippet'>anchor</a></sup>
6161
<!-- endSnippet -->
6262

6363
`HttpRequestInterceptionBuilder` objects are mutable, so properties can be freely changed once a particular setup has been registered with an instance of `HttpClientInterceptorOptions` as the state is captured at the point of registration. This allows multiple responses and paths to be configured from a single `HttpRequestInterceptionBuilder` instance where multiple registrations against a common hostname.
@@ -143,7 +143,7 @@ var client = options.CreateHttpClient();
143143
await Assert.ThrowsAsync<HttpRequestException>(
144144
() => client.GetStringAsync("http://public.je-apis.com"));
145145
```
146-
<sup><a href='/tests/HttpClientInterception.Tests/Examples.cs#L23-L38' title='Snippet source file'>snippet source</a> | <a href='#snippet-fault-injection' title='Start of snippet'>anchor</a></sup>
146+
<sup><a href='/tests/HttpClientInterception.Tests/Examples.cs#L25-L40' title='Snippet source file'>snippet source</a> | <a href='#snippet-fault-injection' title='Start of snippet'>anchor</a></sup>
147147
<!-- endSnippet -->
148148

149149
#### Registering Request Interception When Using IHttpClientFactory

src/HttpClientInterception/Bundles/BundleFactory.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,11 @@ namespace JustEat.HttpClientInterception.Bundles;
1010
/// </summary>
1111
internal static class BundleFactory
1212
{
13+
#if !NET6_0_OR_GREATER
1314
/// <summary>
1415
/// Gets the JSON serializer settings to use.
1516
/// </summary>
16-
private static JsonSerializerOptions Settings { get; } =
17-
#if NET7_0_OR_GREATER
18-
JsonSerializerOptions.Default;
19-
#else
20-
new();
17+
private static JsonSerializerOptions Settings { get; } = new();
2118
#endif
2219

2320
/// <summary>
@@ -30,7 +27,11 @@ internal static class BundleFactory
3027
public static Bundle? Create(string path)
3128
{
3229
string json = File.ReadAllText(path);
30+
#if NET6_0_OR_GREATER
31+
return JsonSerializer.Deserialize(json, BundleJsonSerializerContext.Default.Bundle);
32+
#else
3333
return JsonSerializer.Deserialize<Bundle>(json, Settings);
34+
#endif
3435
}
3536

3637
/// <summary>
@@ -45,6 +46,11 @@ internal static class BundleFactory
4546
public static async ValueTask<Bundle?> CreateAsync(string path, CancellationToken cancellationToken)
4647
{
4748
using var stream = File.OpenRead(path);
49+
50+
#if NET6_0_OR_GREATER
51+
return await JsonSerializer.DeserializeAsync(stream, BundleJsonSerializerContext.Default.Bundle, cancellationToken).ConfigureAwait(false);
52+
#else
4853
return await JsonSerializer.DeserializeAsync<Bundle>(stream, Settings, cancellationToken).ConfigureAwait(false);
54+
#endif
4955
}
5056
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) Just Eat, 2017. All rights reserved.
2+
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
3+
4+
#if NET6_0_OR_GREATER
5+
using System.Text.Json.Serialization;
6+
7+
namespace JustEat.HttpClientInterception.Bundles;
8+
9+
[JsonSerializable(typeof(Bundle))]
10+
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
11+
internal sealed partial class BundleJsonSerializerContext : JsonSerializerContext
12+
{
13+
}
14+
#endif

src/HttpClientInterception/HttpClientInterceptorOptionsExtensions.cs

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
using System.Net;
77
using System.Text;
88
using System.Text.Json;
9+
#if NET6_0_OR_GREATER
10+
using System.Text.Json.Serialization.Metadata;
11+
#endif
912

1013
namespace JustEat.HttpClientInterception;
1114

@@ -188,6 +191,12 @@ public static HttpClientInterceptorOptions RegisterStream(
188191
/// <exception cref="ArgumentNullException">
189192
/// <paramref name="options"/> or <paramref name="content"/> is <see langword="null"/>.
190193
/// </exception>
194+
#if NET6_0_OR_GREATER
195+
[RequiresUnreferencedCode(WarningMessages.SerializationUnreferencedCodeMessage)]
196+
#if NET7_0_OR_GREATER
197+
[RequiresDynamicCode(WarningMessages.SerializationRequiresDynamicCodeMessage)]
198+
#endif
199+
#endif
191200
public static HttpClientInterceptorOptions RegisterGetJson(
192201
this HttpClientInterceptorOptions options,
193202
[StringSyntax(StringSyntaxAttribute.Uri)]
@@ -205,14 +214,43 @@ public static HttpClientInterceptorOptions RegisterGetJson(
205214
throw new ArgumentNullException(nameof(content));
206215
}
207216

208-
byte[] ContentFactory()
209-
{
210-
string json = JsonSerializer.Serialize(content);
211-
return Encoding.UTF8.GetBytes(json);
212-
}
217+
byte[] ContentFactory() => JsonSerializer.SerializeToUtf8Bytes(content);
218+
219+
return options.RegisterByteArray(HttpMethod.Get, new Uri(uriString), ContentFactory, statusCode);
220+
}
221+
222+
#if NET6_0_OR_GREATER
223+
/// <summary>
224+
/// Registers an HTTP GET request for the specified JSON content.
225+
/// </summary>
226+
/// <typeparam name="T">The type of the object to serialize as JSON.</typeparam>
227+
/// <param name="options">The <see cref="HttpClientInterceptorOptions"/> to set up.</param>
228+
/// <param name="uriString">The request URL.</param>
229+
/// <param name="content">The object to serialize as JSON as the content.</param>
230+
/// <param name="jsonTypeInfo">The <see cref="JsonTypeInfo{T}"/> to use.</param>
231+
/// <param name="statusCode">The optional HTTP status code to return.</param>
232+
/// <returns>
233+
/// The value specified by <paramref name="options"/>.
234+
/// </returns>
235+
/// <exception cref="ArgumentNullException">
236+
/// <paramref name="options"/> or <paramref name="jsonTypeInfo"/> is <see langword="null"/>.
237+
/// </exception>
238+
public static HttpClientInterceptorOptions RegisterGetFromJson<T>(
239+
this HttpClientInterceptorOptions options,
240+
[StringSyntax(StringSyntaxAttribute.Uri)]
241+
string uriString,
242+
T content,
243+
JsonTypeInfo<T> jsonTypeInfo,
244+
HttpStatusCode statusCode = HttpStatusCode.OK)
245+
{
246+
ArgumentNullException.ThrowIfNull(options);
247+
ArgumentNullException.ThrowIfNull(jsonTypeInfo);
248+
249+
byte[] ContentFactory() => JsonSerializer.SerializeToUtf8Bytes(content, jsonTypeInfo);
213250

214251
return options.RegisterByteArray(HttpMethod.Get, new Uri(uriString), ContentFactory, statusCode);
215252
}
253+
#endif
216254

217255
/// <summary>
218256
/// Registers an HTTP GET request for the specified string.

src/HttpClientInterception/HttpRequestInterceptionBuilderExtensions.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
using System.ComponentModel;
55
using System.Diagnostics.CodeAnalysis;
66
using System.Text;
7+
using System.Text.Json;
8+
#if NET6_0_OR_GREATER
9+
using System.Text.Json.Serialization.Metadata;
10+
#endif
711
using Microsoft.AspNetCore.WebUtilities;
812

913
namespace JustEat.HttpClientInterception;
@@ -202,13 +206,53 @@ async Task<byte[]> ContentFactoryAsync()
202206
/// <exception cref="ArgumentNullException">
203207
/// <paramref name="builder"/> or <paramref name="content"/> is <see langword="null"/>.
204208
/// </exception>
209+
#if NET6_0_OR_GREATER
210+
[RequiresUnreferencedCode(WarningMessages.SerializationUnreferencedCodeMessage)]
211+
#if NET7_0_OR_GREATER
212+
[RequiresDynamicCode(WarningMessages.SerializationRequiresDynamicCodeMessage)]
213+
#endif
214+
#endif
205215
public static HttpRequestInterceptionBuilder WithJsonContent(
206216
this HttpRequestInterceptionBuilder builder,
207217
object content)
208218
{
209219
return builder.WithSystemTextJsonContent(content);
210220
}
211221

222+
#if NET6_0_OR_GREATER
223+
/// <summary>
224+
/// Sets the object to use as the response content.
225+
/// </summary>
226+
/// <typeparam name="T">The type of the object to serialize as JSON.</typeparam>
227+
/// <param name="builder">The <see cref="HttpRequestInterceptionBuilder"/> to use.</param>
228+
/// <param name="content">The object to serialize as JSON as the content.</param>
229+
/// <param name="jsonTypeInfo">The <see cref="JsonTypeInfo{T}"/> to use.</param>
230+
/// <returns>
231+
/// The value specified by <paramref name="builder"/>.
232+
/// </returns>
233+
/// <exception cref="ArgumentNullException">
234+
/// <paramref name="builder"/>, <paramref name="content"/> or <paramref name="jsonTypeInfo"/> is <see langword="null"/>.
235+
/// </exception>
236+
public static HttpRequestInterceptionBuilder WithJsonContent<T>(
237+
this HttpRequestInterceptionBuilder builder,
238+
T content,
239+
JsonTypeInfo<T> jsonTypeInfo)
240+
{
241+
ArgumentNullException.ThrowIfNull(builder);
242+
ArgumentNullException.ThrowIfNull(content);
243+
ArgumentNullException.ThrowIfNull(jsonTypeInfo);
244+
245+
byte[] ContentFactory()
246+
{
247+
return JsonSerializer.SerializeToUtf8Bytes(content, jsonTypeInfo);
248+
}
249+
250+
return builder
251+
.WithMediaType(HttpClientInterceptorOptions.JsonMediaType)
252+
.WithContent(ContentFactory);
253+
}
254+
#endif
255+
212256
/// <summary>
213257
/// Sets the request URL to intercept a request for.
214258
/// </summary>

src/HttpClientInterception/JustEat.HttpClientInterception.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,11 @@
2626
<AdditionalFiles Include="PublicAPI/$(TargetFramework)/PublicAPI.Shipped.txt" />
2727
<AdditionalFiles Include="PublicAPI/$(TargetFramework)/PublicAPI.Unshipped.txt" />
2828
</ItemGroup>
29+
<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))">
30+
<EnableAotAnalyzer>true</EnableAotAnalyzer>
31+
<EnableSingleFileAnalyzer>true</EnableSingleFileAnalyzer>
32+
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
33+
<IsAotCompatible>true</IsAotCompatible>
34+
<IsTrimmable>true</IsTrimmable>
35+
</PropertyGroup>
2936
</Project>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
#nullable enable
2+
static JustEat.HttpClientInterception.HttpClientInterceptorOptionsExtensions.RegisterGetFromJson<T>(this JustEat.HttpClientInterception.HttpClientInterceptorOptions! options, string! uriString, T content, System.Text.Json.Serialization.Metadata.JsonTypeInfo<T>! jsonTypeInfo, System.Net.HttpStatusCode statusCode = System.Net.HttpStatusCode.OK) -> JustEat.HttpClientInterception.HttpClientInterceptorOptions!
3+
static JustEat.HttpClientInterception.HttpRequestInterceptionBuilderExtensions.WithJsonContent<T>(this JustEat.HttpClientInterception.HttpRequestInterceptionBuilder! builder, T content, System.Text.Json.Serialization.Metadata.JsonTypeInfo<T>! jsonTypeInfo) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
#nullable enable
2+
static JustEat.HttpClientInterception.HttpClientInterceptorOptionsExtensions.RegisterGetFromJson<T>(this JustEat.HttpClientInterception.HttpClientInterceptorOptions! options, string! uriString, T content, System.Text.Json.Serialization.Metadata.JsonTypeInfo<T>! jsonTypeInfo, System.Net.HttpStatusCode statusCode = System.Net.HttpStatusCode.OK) -> JustEat.HttpClientInterception.HttpClientInterceptorOptions!
3+
static JustEat.HttpClientInterception.HttpRequestInterceptionBuilderExtensions.WithJsonContent<T>(this JustEat.HttpClientInterception.HttpRequestInterceptionBuilder! builder, T content, System.Text.Json.Serialization.Metadata.JsonTypeInfo<T>! jsonTypeInfo) -> JustEat.HttpClientInterception.HttpRequestInterceptionBuilder!

src/HttpClientInterception/SystemTextJsonExtensions.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
33

44
using System.ComponentModel;
5+
using System.Diagnostics.CodeAnalysis;
56
using System.Text.Json;
67

78
namespace JustEat.HttpClientInterception;
@@ -25,6 +26,12 @@ public static class SystemTextJsonExtensions
2526
/// <exception cref="ArgumentNullException">
2627
/// <paramref name="builder"/> or <paramref name="content"/> is <see langword="null"/>.
2728
/// </exception>
29+
#if NET6_0_OR_GREATER
30+
[RequiresUnreferencedCode(WarningMessages.SerializationUnreferencedCodeMessage)]
31+
#if NET7_0_OR_GREATER
32+
[RequiresDynamicCode(WarningMessages.SerializationRequiresDynamicCodeMessage)]
33+
#endif
34+
#endif
2835
public static HttpRequestInterceptionBuilder WithSystemTextJsonContent(
2936
this HttpRequestInterceptionBuilder builder,
3037
object content,

0 commit comments

Comments
 (0)