Skip to content

Commit f2032c2

Browse files
committed
Add RpcTargetMetadata.FromShape<T> overloads that work on .NET Framework
1 parent 5beb5c3 commit f2032c2

File tree

5 files changed

+82
-5
lines changed

5 files changed

+82
-5
lines changed

src/StreamJsonRpc/JsonMessageFormatter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,7 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer
10501050
/// Converts an enumeration token to an <see cref="IAsyncEnumerable{T}"/>.
10511051
/// </summary>
10521052
[RequiresDynamicCode(RuntimeReasons.CloseGenerics)]
1053+
[RequiresUnreferencedCode(RuntimeReasons.CloseGenerics)]
10531054
private class AsyncEnumerableConsumerConverter : JsonConverter
10541055
{
10551056
private static readonly MethodInfo ReadJsonOpenGenericMethod = typeof(AsyncEnumerableConsumerConverter).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).Single(m => m.Name == nameof(ReadJson) && m.IsGenericMethod);
@@ -1109,6 +1110,7 @@ private IAsyncEnumerable<T> ReadJson<T>(JsonReader reader, JsonSerializer serial
11091110
/// Converts an instance of <see cref="IAsyncEnumerable{T}"/> to an enumeration token.
11101111
/// </summary>
11111112
[RequiresDynamicCode(RuntimeReasons.CloseGenerics)]
1113+
[RequiresUnreferencedCode(RuntimeReasons.CloseGenerics)]
11121114
private class AsyncEnumerableGeneratorConverter : JsonConverter
11131115
{
11141116
private static readonly MethodInfo WriteJsonOpenGenericMethod = typeof(AsyncEnumerableGeneratorConverter).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Single(m => m.Name == nameof(WriteJson) && m.IsGenericMethod);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System.ComponentModel;
2+
using System.Diagnostics.CodeAnalysis;
3+
using PolyType.Abstractions;
4+
5+
namespace StreamJsonRpc;
6+
7+
/// <summary>
8+
/// Extension methods for the <see cref="RpcTargetMetadata"/> class.
9+
/// </summary>
10+
public static class RpcTargetMetadataExtensions
11+
{
12+
extension(RpcTargetMetadata)
13+
{
14+
/// <summary>
15+
/// Creates an <see cref="RpcTargetMetadata"/> instance from the specified shape.
16+
/// </summary>
17+
/// <typeparam name="T">The type for which a shape should be obtained and <see cref="RpcTargetMetadata"/> generated for.</typeparam>
18+
/// <returns>An <see cref="RpcTargetMetadata"/> instance initialized from the shape of the <typeparamref name="T"/>.</returns>
19+
#if NET8_0
20+
[RequiresDynamicCode(ResolveDynamicMessage)]
21+
#endif
22+
#if NET
23+
[EditorBrowsable(EditorBrowsableState.Never)]
24+
[Obsolete("Use the RpcTargetMetadata.FromShape<T>() method instead. If using the extension method syntax, check that your type argument actually has a [GenerateShape] attribute or otherwise implements IShapeable<T> to avoid a runtime failure.", error: true)]
25+
#endif
26+
public static RpcTargetMetadata FromShape<T>()
27+
=> RpcTargetMetadata.FromShape(TypeShapeResolver.ResolveDynamicOrThrow<T>());
28+
29+
/// <summary>
30+
/// Creates an <see cref="RpcTargetMetadata"/> instance from the specified shape.
31+
/// </summary>
32+
/// <typeparam name="T">The type for which a shape should be obtained and <see cref="RpcTargetMetadata"/> generated for.</typeparam>
33+
/// <typeparam name="TProvider">The provider of type shapes from which to obtain the shape.</typeparam>
34+
/// <returns>An <see cref="RpcTargetMetadata"/> instance initialized from the shape of the <typeparamref name="T"/>.</returns>
35+
#if NET8_0
36+
[RequiresDynamicCode(ResolveDynamicMessage)]
37+
#endif
38+
#if NET
39+
[EditorBrowsable(EditorBrowsableState.Never)]
40+
[Obsolete("Use the RpcTargetMetadata.FromShape<T, TProvider>() method instead. If using the extension method syntax, check that your type argument actually has a [GenerateShape] attribute or otherwise implements IShapeable<T> to avoid a runtime failure.", error: true)]
41+
#endif
42+
public static RpcTargetMetadata FromShape<T, TProvider>()
43+
=> RpcTargetMetadata.FromShape(TypeShapeResolver.ResolveDynamicOrThrow<T, TProvider>());
44+
}
45+
46+
#if NET8_0
47+
/// <summary>
48+
/// A message to use as the argument to <see cref="RequiresDynamicCodeAttribute"/>
49+
/// for methods that call into <see cref="TypeShapeResolver.ResolveDynamicOrThrow{T}"/>.
50+
/// </summary>
51+
/// <seealso href="https://github.com/dotnet/runtime/issues/119440#issuecomment-3269894751"/>
52+
private const string ResolveDynamicMessage =
53+
"Dynamic resolution of IShapeable<T> interface may require dynamic code generation in .NET 8 Native AOT. " +
54+
"It is recommended to switch to statically resolved IShapeable<T> APIs or upgrade your app to .NET 9 or later.";
55+
#endif
56+
}

src/StreamJsonRpc/StreamJsonRpc.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0</TargetFrameworks>
3+
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0</TargetFrameworks>
44
<ErrorReport>prompt</ErrorReport>
55
<WarningLevel>4</WarningLevel>
66
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

test/StreamJsonRpc.Tests/RpcTargetMetadataTests.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4-
using PolyType;
5-
64
public partial class RpcTargetMetadataTests
75
{
86
internal interface IRpcContractBase
@@ -45,7 +43,27 @@ internal partial interface IShapedContract
4543
[Fact]
4644
public void FromShape()
4745
{
48-
RpcTargetMetadata metadata = RpcTargetMetadata.FromShape<IShapedContract>(Witness.GeneratedTypeShapeProvider);
46+
RpcTargetMetadata metadata = RpcTargetMetadata.FromShape<IShapedContract>();
47+
48+
var addAsync = Assert.Single(metadata.Methods["AddAsync"]);
49+
var add = Assert.Single(metadata.AliasedMethods["Add"]);
50+
Assert.Same(addAsync, add);
51+
52+
var subtract = Assert.Single(metadata.Methods["Subtract"]);
53+
Assert.False(metadata.Methods.ContainsKey("SubtractAsync"));
54+
55+
// Verify that JsonRpcMethod.Name takes precedence over MethodShape.Name.
56+
var multiply = Assert.Single(metadata.Methods["Times"]);
57+
58+
// Fail the test when support for events is added so we can update the test.
59+
Assert.Equal(3, metadata.Methods.Count);
60+
Assert.Single(metadata.AliasedMethods);
61+
}
62+
63+
[Fact]
64+
public void FromShape_TProvider()
65+
{
66+
RpcTargetMetadata metadata = RpcTargetMetadata.FromShape<IShapedContract, Witness>();
4967

5068
var addAsync = Assert.Single(metadata.Methods["AddAsync"]);
5169
var add = Assert.Single(metadata.AliasedMethods["Add"]);
@@ -130,5 +148,6 @@ internal class RpcContractDerivedClass : IRpcContractDerived
130148
}
131149

132150
[GenerateShapeFor<bool>]
151+
[GenerateShapeFor<IShapedContract>]
133152
private partial class Witness;
134153
}

test/StreamJsonRpc.Tests/StreamJsonRpc.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net8.0</TargetFrameworks>
4+
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
55
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('Windows'))">$(TargetFrameworks);net472</TargetFrameworks>
66
<OutputType>exe</OutputType>
77
<RootNamespace />

0 commit comments

Comments
 (0)