diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a7155c4c..30eadd85 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -32,6 +32,8 @@ jobs: - run: dotnet docfx docfx/docfx.json name: 📚 Generate documentation + env: + DocFx: true # Workaround https://github.com/dotnet/docfx/issues/10808 - name: Upload artifact uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4 diff --git a/.github/workflows/docs_validate.yml b/.github/workflows/docs_validate.yml index a6bbb1e8..f07e11d0 100644 --- a/.github/workflows/docs_validate.yml +++ b/.github/workflows/docs_validate.yml @@ -29,3 +29,5 @@ jobs: run: dotnet build -c Release src/StreamJsonRpc.Analyzers.CodeFixes - name: 📚 Verify docfx build run: dotnet docfx docfx/docfx.json --warningsAsErrors --disableGitFeatures + env: + DocFx: true # Workaround https://github.com/dotnet/docfx/issues/10808 diff --git a/Directory.Build.props b/Directory.Build.props index cd89f8e7..a7be4067 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -38,6 +38,9 @@ true true snupkg + + + $(DefineConstants);NOTDOCFX diff --git a/src/StreamJsonRpc/JsonMessageFormatter.cs b/src/StreamJsonRpc/JsonMessageFormatter.cs index 1d4d153e..e9fe066f 100644 --- a/src/StreamJsonRpc/JsonMessageFormatter.cs +++ b/src/StreamJsonRpc/JsonMessageFormatter.cs @@ -1050,6 +1050,7 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer /// Converts an enumeration token to an . /// [RequiresDynamicCode(RuntimeReasons.CloseGenerics)] + [RequiresUnreferencedCode(RuntimeReasons.CloseGenerics)] private class AsyncEnumerableConsumerConverter : JsonConverter { 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 ReadJson(JsonReader reader, JsonSerializer serial /// Converts an instance of to an enumeration token. /// [RequiresDynamicCode(RuntimeReasons.CloseGenerics)] + [RequiresUnreferencedCode(RuntimeReasons.CloseGenerics)] private class AsyncEnumerableGeneratorConverter : JsonConverter { private static readonly MethodInfo WriteJsonOpenGenericMethod = typeof(AsyncEnumerableGeneratorConverter).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Single(m => m.Name == nameof(WriteJson) && m.IsGenericMethod); diff --git a/src/StreamJsonRpc/RpcTargetMetadata.cs b/src/StreamJsonRpc/RpcTargetMetadata.cs index 43ae0317..5cd8285f 100644 --- a/src/StreamJsonRpc/RpcTargetMetadata.cs +++ b/src/StreamJsonRpc/RpcTargetMetadata.cs @@ -293,13 +293,6 @@ public static RpcTargetMetadata FromShape() where TProvider : IShapeable => FromShape(TProvider.GetTypeShape()); #endif - /// - /// The type for which a shape should be obtained and generated for. - /// The provider of type shapes from which to obtain the shape. - /// An instance initialized from the shape of the . - [Obsolete("Use FromShape(ITypeShape) instead. This API will be removed soon.")] - public static RpcTargetMetadata FromShape(ITypeShapeProvider provider) => FromShape(provider.GetTypeShapeOrThrow()); - /// /// Creates an instance from the specified shape. /// diff --git a/src/StreamJsonRpc/RpcTargetMetadataExtensions.cs b/src/StreamJsonRpc/RpcTargetMetadataExtensions.cs new file mode 100644 index 00000000..61425b74 --- /dev/null +++ b/src/StreamJsonRpc/RpcTargetMetadataExtensions.cs @@ -0,0 +1,60 @@ +#if NOTDOCFX // Workaround https://github.com/dotnet/docfx/issues/10808 + +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using PolyType.Abstractions; + +namespace StreamJsonRpc; + +/// +/// Extension methods for the class. +/// +public static class RpcTargetMetadataExtensions +{ + extension(RpcTargetMetadata) + { + /// + /// Creates an instance from the specified shape. + /// + /// The type for which a shape should be obtained and generated for. + /// An instance initialized from the shape of the . +#if NET8_0 + [RequiresDynamicCode(ResolveDynamicMessage)] +#endif +#if NET + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use the RpcTargetMetadata.FromShape() method instead. If using the extension method syntax, check that your type argument actually has a [GenerateShape] attribute or otherwise implements IShapeable to avoid a runtime failure.", error: true)] +#endif + public static RpcTargetMetadata FromShape() + => RpcTargetMetadata.FromShape(TypeShapeResolver.ResolveDynamicOrThrow()); + + /// + /// Creates an instance from the specified shape. + /// + /// The type for which a shape should be obtained and generated for. + /// The provider of type shapes from which to obtain the shape. + /// An instance initialized from the shape of the . +#if NET8_0 + [RequiresDynamicCode(ResolveDynamicMessage)] +#endif +#if NET + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Use the RpcTargetMetadata.FromShape() method instead. If using the extension method syntax, check that your type argument actually has a [GenerateShape] attribute or otherwise implements IShapeable to avoid a runtime failure.", error: true)] +#endif + public static RpcTargetMetadata FromShape() + => RpcTargetMetadata.FromShape(TypeShapeResolver.ResolveDynamicOrThrow()); + } + +#if NET8_0 + /// + /// A message to use as the argument to + /// for methods that call into . + /// + /// + private const string ResolveDynamicMessage = + "Dynamic resolution of IShapeable interface may require dynamic code generation in .NET 8 Native AOT. " + + "It is recommended to switch to statically resolved IShapeable APIs or upgrade your app to .NET 9 or later."; +#endif +} + +#endif diff --git a/src/StreamJsonRpc/StreamJsonRpc.csproj b/src/StreamJsonRpc/StreamJsonRpc.csproj index 4a278bff..bcc6ee61 100644 --- a/src/StreamJsonRpc/StreamJsonRpc.csproj +++ b/src/StreamJsonRpc/StreamJsonRpc.csproj @@ -1,6 +1,6 @@  - netstandard2.0;netstandard2.1;net8.0 + netstandard2.0;netstandard2.1;net8.0;net9.0 prompt 4 true diff --git a/test/StreamJsonRpc.Tests/RpcTargetMetadataTests.cs b/test/StreamJsonRpc.Tests/RpcTargetMetadataTests.cs index c4c39558..3291c450 100644 --- a/test/StreamJsonRpc.Tests/RpcTargetMetadataTests.cs +++ b/test/StreamJsonRpc.Tests/RpcTargetMetadataTests.cs @@ -43,7 +43,27 @@ internal partial interface IShapedContract [Fact] public void FromShape() { - RpcTargetMetadata metadata = RpcTargetMetadata.FromShape(PolyType.SourceGenerator.TypeShapeProvider_StreamJsonRpc_Tests.Default.IShapedContract); + RpcTargetMetadata metadata = RpcTargetMetadata.FromShape(); + + var addAsync = Assert.Single(metadata.Methods["AddAsync"]); + var add = Assert.Single(metadata.AliasedMethods["Add"]); + Assert.Same(addAsync, add); + + var subtract = Assert.Single(metadata.Methods["Subtract"]); + Assert.False(metadata.Methods.ContainsKey("SubtractAsync")); + + // Verify that JsonRpcMethod.Name takes precedence over MethodShape.Name. + var multiply = Assert.Single(metadata.Methods["Times"]); + + // Fail the test when support for events is added so we can update the test. + Assert.Equal(3, metadata.Methods.Count); + Assert.Single(metadata.AliasedMethods); + } + + [Fact] + public void FromShape_TProvider() + { + RpcTargetMetadata metadata = RpcTargetMetadata.FromShape(); var addAsync = Assert.Single(metadata.Methods["AddAsync"]); var add = Assert.Single(metadata.AliasedMethods["Add"]); @@ -128,5 +148,6 @@ internal class RpcContractDerivedClass : IRpcContractDerived } [GenerateShapeFor] + [GenerateShapeFor] private partial class Witness; } diff --git a/test/StreamJsonRpc.Tests/StreamJsonRpc.Tests.csproj b/test/StreamJsonRpc.Tests/StreamJsonRpc.Tests.csproj index 53cda5e5..85ad9aa7 100644 --- a/test/StreamJsonRpc.Tests/StreamJsonRpc.Tests.csproj +++ b/test/StreamJsonRpc.Tests/StreamJsonRpc.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net8.0;net9.0 $(TargetFrameworks);net472 exe