diff --git a/Directory.Packages.props b/Directory.Packages.props index b8d1c8d88..b80856672 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -32,6 +32,7 @@ + diff --git a/src/StreamJsonRpc/ProxyGeneration.cs b/src/StreamJsonRpc/ProxyGeneration.cs index 7b4cae738..2ad37b363 100644 --- a/src/StreamJsonRpc/ProxyGeneration.cs +++ b/src/StreamJsonRpc/ProxyGeneration.cs @@ -302,7 +302,7 @@ internal static TypeInfo Get(Type contractInterface, ReadOnlySpan addition { foreach (RpcTargetMetadata.TargetMethodMetadata methodMetadata in overloads) { - MethodInfo method = methodMetadata.Method; + MethodInfo method = methodMetadata.MethodInfo; if (!implementedMethods.Add(method)) { continue; diff --git a/src/StreamJsonRpc/Reflection/RpcTargetInfo.cs b/src/StreamJsonRpc/Reflection/RpcTargetInfo.cs index 51829ba71..70f6437c8 100644 --- a/src/StreamJsonRpc/Reflection/RpcTargetInfo.cs +++ b/src/StreamJsonRpc/Reflection/RpcTargetInfo.cs @@ -238,7 +238,7 @@ internal void AddLocalRpcMethod(MethodInfo handler, object? target, JsonRpcMetho string rpcMethodName = methodRpcSettings?.Name ?? handler.Name; lock (this.SyncObject) { - MethodSignatureAndTarget methodTarget = new(RpcTargetMetadata.TargetMethodMetadata.From(handler, methodRpcSettings), target, attribute: null, synchronizationContext); + MethodSignatureAndTarget methodTarget = new(RpcTargetMetadata.TargetMethodMetadata.From(handler, methodRpcSettings, shape: null), target, attribute: null, synchronizationContext); this.TraceLocalMethodAdded(rpcMethodName, methodTarget); if (this.targetRequestMethodToClrMethodMap.TryGetValue(rpcMethodName, out List? existingList)) { diff --git a/src/StreamJsonRpc/Reflection/TargetMethod.cs b/src/StreamJsonRpc/Reflection/TargetMethod.cs index 50522f82b..6c15a5048 100644 --- a/src/StreamJsonRpc/Reflection/TargetMethod.cs +++ b/src/StreamJsonRpc/Reflection/TargetMethod.cs @@ -82,7 +82,7 @@ internal TargetMethod( /// /// Gets the that will be invoked to handle the request, if one was found. /// - public MethodInfo? TargetMethodInfo => this.signature?.Method; + public MethodInfo? TargetMethodInfo => this.signature?.MethodInfo; /// /// Gets all the exceptions thrown while trying to deserialize arguments to candidate parameter types. @@ -107,12 +107,12 @@ internal string LookupErrorMessage } } - internal Type? ReturnType => this.signature?.Method.ReturnType; + internal Type? ReturnType => this.signature?.ReturnType; /// public override string ToString() { - return this.signature is not null ? $"{this.signature.Method.DeclaringType!.FullName}.{this.signature.Name}({this.GetParameterSignature()})" : ""; + return this.signature is not null ? $"{this.signature.MethodInfo.DeclaringType?.FullName}.{this.signature.Name}({this.GetParameterSignature()})" : ""; } internal async Task InvokeAsync(CancellationToken cancellationToken) @@ -130,7 +130,7 @@ public override string ToString() Assumes.NotNull(this.synchronizationContext); await this.synchronizationContext; - return this.signature.Method.Invoke(!this.signature.Method.IsStatic ? this.target : null, this.arguments); + return this.signature.MethodInfo.Invoke(!this.signature.MethodInfo.IsStatic ? this.target : null, this.arguments); } private string? GetParameterSignature() => this.signature is not null ? string.Join(", ", this.signature.Parameters.Select(p => p.ParameterType.Name)) : null; diff --git a/src/StreamJsonRpc/Resources.resx b/src/StreamJsonRpc/Resources.resx index 08f4ffd7e..fd78b8e83 100644 --- a/src/StreamJsonRpc/Resources.resx +++ b/src/StreamJsonRpc/Resources.resx @@ -381,4 +381,7 @@ This operation can only be performed once on this object. + + The shape for {member} must include a value for AttributeProvider. + \ No newline at end of file diff --git a/src/StreamJsonRpc/RpcTargetMetadata.cs b/src/StreamJsonRpc/RpcTargetMetadata.cs index f1436d35f..a468e8fb5 100644 --- a/src/StreamJsonRpc/RpcTargetMetadata.cs +++ b/src/StreamJsonRpc/RpcTargetMetadata.cs @@ -9,6 +9,10 @@ using System.Globalization; using System.Reflection; using Microsoft.VisualStudio.Threading; +using PolyType; +using PolyType.Abstractions; +using PolyType.Utilities; +using StreamJsonRpc.Protocol; namespace StreamJsonRpc; @@ -277,6 +281,46 @@ public static RpcTargetMetadata FromClassNonPublic([DynamicallyAccessedMembers(D return NonPublicClass.TryAdd(classType, result) ? result : NonPublicClass[classType]; } +#if NET + /// + /// 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 . + public static RpcTargetMetadata FromShape() + where T : IShapeable => FromShape(T.GetShape()); + + /// + /// 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 . + public static RpcTargetMetadata FromShape() + where TProvider : IShapeable => FromShape(TProvider.GetShape()); +#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 . + public static RpcTargetMetadata FromShape(ITypeShapeProvider provider) => FromShape(provider.Resolve()); + + /// + /// Creates an instance from the specified shape. + /// + /// The shape to create the metadata from. + /// An instance initialized from the . + public static RpcTargetMetadata FromShape(ITypeShape shape) + { + Requires.NotNull(shape); + + Builder builder = new(shape); + AddMethods(builder, shape.Methods); + + return builder.ToImmutable(); + } + /// /// Creates an event handler factory that supports for a given . /// @@ -287,15 +331,23 @@ public static RpcTargetMetadata FromClassNonPublic([DynamicallyAccessedMembers(D public static void RegisterEventArgs() where TEventArgs : struct => EventHandlerFactories.TryAdd(typeof(TEventArgs), new EventHandlerFactory()); + private static void AddMethods(Builder builder, IReadOnlyList methods) + { + foreach (IMethodShape shape in methods) + { + TryAddCandidateMethod(builder, GetMethodInfo(shape), shape); + } + } + private static void AddMethods(Builder builder, IEnumerable methods) { foreach (MethodInfo method in methods) { - TryAddCandidateMethod(builder, method); + TryAddCandidateMethod(builder, method, shape: null); } } - private static bool TryAddCandidateMethod(Builder builder, MethodInfo method) + private static bool TryAddCandidateMethod(Builder builder, MethodInfo method, IMethodShape? shape) { if (method.IsSpecialName || method.IsConstructor || method.DeclaringType == typeof(object)) { @@ -315,14 +367,9 @@ private static bool TryAddCandidateMethod(Builder builder, MethodInfo method) return false; } - var methodMetadata = TargetMethodMetadata.From(method, methodAttribute); + var methodMetadata = TargetMethodMetadata.From(method, methodAttribute, shape); - if (!builder.Methods.TryGetValue(methodMetadata.Name, out List? methodList)) - { - builder.Methods[methodMetadata.Name] = methodList = []; - } - - methodList.Add(methodMetadata); + builder.AddMethod(methodMetadata); return true; } @@ -452,6 +499,8 @@ private static IReadOnlyList GetMissingInterfacesFromSet([DynamicallyAcces return missing ?? []; } + private static MethodInfo GetMethodInfo(IMethodShape shape) => (MethodInfo)(shape.AttributeProvider ?? throw new ArgumentException(Resources.FormatAttributeProviderRequired($"{shape.DeclaringType.Type.FullName}.{shape.Name}"), nameof(shape))); + internal struct RpcTargetInterface([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicEvents)] Type iface) { [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicEvents)] @@ -683,20 +732,33 @@ public class TargetMethodMetadata { private ParameterInfo[]? parameters; + internal TargetMethodMetadata(MethodInfo method, JsonRpcMethodAttribute? attribute, IMethodShape? shape) + { + this.IsPublic = method.IsPublic; + this.Name = attribute?.Name ?? shape?.Name ?? method.Name; + this.MethodInfo = method; + this.Attribute = attribute; + + this.ReturnType = method.ReturnType; + ParameterInfo[] parameters = method.GetParameters(); + this.RequiredParamCount = parameters.Count(pi => !pi.IsOptional && pi.ParameterType != typeof(CancellationToken)); + this.HasCancellationTokenParameter = parameters is [.., { ParameterType: { } type }] && type == typeof(CancellationToken); + } + /// /// Gets the for the RPC target method. /// - public required MethodInfo Method { get; init; } + public MethodInfo MethodInfo { get; } /// /// Gets the RPC target name that should invoke this method. /// - public required string Name { get; init; } + public string Name { get; } /// /// Gets the that applies to this method, if any. /// - public required JsonRpcMethodAttribute? Attribute { get; init; } + public JsonRpcMethodAttribute? Attribute { get; } /// /// Gets the parameters on the method. @@ -704,7 +766,7 @@ public class TargetMethodMetadata /// /// This is equivalent to , but cached for performance. /// - internal IReadOnlyList Parameters => this.parameters ??= this.Method.GetParameters() ?? []; + internal IReadOnlyList Parameters => this.parameters ??= this.MethodInfo.GetParameters() ?? []; /// /// Gets a view of the parameters on the method. @@ -712,32 +774,28 @@ public class TargetMethodMetadata /// internal ReadOnlyMemory ParametersMemory => (ParameterInfo[])this.Parameters; + internal Type ReturnType { get; } + /// /// Gets a value indicating whether the method is declared as public. /// - internal bool IsPublic => this.Method.IsPublic; + internal bool IsPublic { get; } - internal int RequiredParamCount => this.Parameters.Count(pi => !pi.IsOptional && pi.ParameterType != typeof(CancellationToken)); + internal int RequiredParamCount { get; } internal int TotalParamCountExcludingCancellationToken => this.HasCancellationTokenParameter ? this.Parameters.Count - 1 : this.Parameters.Count; - internal bool HasCancellationTokenParameter => this.Parameters is [.., { ParameterType: { } type }] && type == typeof(CancellationToken); + internal bool HasCancellationTokenParameter { get; } internal bool HasOutOrRefParameters => this.Parameters.Any(pi => pi.IsOut || pi.ParameterType.IsByRef); [ExcludeFromCodeCoverage] - private string DebuggerDisplay => $"{this.Method.DeclaringType}.{this.Name}({string.Join(", ", this.Parameters.Select(p => p.ParameterType.Name))})"; + private string DebuggerDisplay => $"{this.MethodInfo.DeclaringType}.{this.Name}({string.Join(", ", this.Parameters.Select(p => p.ParameterType.Name))})"; /// public override string ToString() => this.DebuggerDisplay; - internal static TargetMethodMetadata From(MethodInfo method, JsonRpcMethodAttribute? attribute) - => new() - { - Method = method, - Name = attribute?.Name ?? method.Name, - Attribute = attribute, - }; + internal static TargetMethodMetadata From(MethodInfo method, JsonRpcMethodAttribute? attribute, IMethodShape? shape) => new(method, attribute, shape); internal bool EqualSignature(TargetMethodMetadata other) { @@ -793,6 +851,11 @@ public Delegate CreateEventHandler(JsonRpc rpc, string eventName, Type delegateT private class Builder { + internal Builder(ITypeShape shape) + { + this.TargetType = shape.Type; + } + internal Builder(InterfaceCollection interfaces) { this.TargetType = interfaces.PrimaryInterface; @@ -819,6 +882,16 @@ internal Builder(ClassAndInterfaces classAndInterfaces) internal List Events { get; } = []; + internal void AddMethod(TargetMethodMetadata methodMetadata) + { + if (!this.Methods.TryGetValue(methodMetadata.Name, out List? methodList)) + { + this.Methods[methodMetadata.Name] = methodList = []; + } + + methodList.Add(methodMetadata); + } + internal RpcTargetMetadata ToImmutable() { this.GenerateAliases(); diff --git a/src/StreamJsonRpc/StreamJsonRpc.csproj b/src/StreamJsonRpc/StreamJsonRpc.csproj index 9ade22ca9..cab5e8ca3 100644 --- a/src/StreamJsonRpc/StreamJsonRpc.csproj +++ b/src/StreamJsonRpc/StreamJsonRpc.csproj @@ -13,7 +13,7 @@ true - + diff --git a/src/StreamJsonRpc/net8.0/PublicAPI.Unshipped.txt b/src/StreamJsonRpc/net8.0/PublicAPI.Unshipped.txt index 432de8d70..fcf306724 100644 --- a/src/StreamJsonRpc/net8.0/PublicAPI.Unshipped.txt +++ b/src/StreamJsonRpc/net8.0/PublicAPI.Unshipped.txt @@ -12,6 +12,10 @@ static StreamJsonRpc.RpcTargetMetadata.FromClassNonPublic(System.Type! classType static StreamJsonRpc.RpcTargetMetadata.FromClassNonPublic(System.Type! classType, StreamJsonRpc.RpcTargetMetadata.ClassAndInterfaces! metadata) -> StreamJsonRpc.RpcTargetMetadata! static StreamJsonRpc.RpcTargetMetadata.FromInterface(StreamJsonRpc.RpcTargetMetadata.InterfaceCollection! interfaces) -> StreamJsonRpc.RpcTargetMetadata! static StreamJsonRpc.RpcTargetMetadata.FromInterface(System.Type! rpcContract) -> StreamJsonRpc.RpcTargetMetadata! +static StreamJsonRpc.RpcTargetMetadata.FromShape(PolyType.Abstractions.ITypeShape! shape) -> StreamJsonRpc.RpcTargetMetadata! +static StreamJsonRpc.RpcTargetMetadata.FromShape() -> StreamJsonRpc.RpcTargetMetadata! +static StreamJsonRpc.RpcTargetMetadata.FromShape() -> StreamJsonRpc.RpcTargetMetadata! +static StreamJsonRpc.RpcTargetMetadata.FromShape(PolyType.ITypeShapeProvider! provider) -> StreamJsonRpc.RpcTargetMetadata! static StreamJsonRpc.RpcTargetMetadata.RegisterEventArgs() -> void StreamJsonRpc.ExportRpcContractProxiesAttribute StreamJsonRpc.ExportRpcContractProxiesAttribute.ExportRpcContractProxiesAttribute() -> void @@ -117,12 +121,8 @@ StreamJsonRpc.RpcTargetMetadata.Methods.init -> void StreamJsonRpc.RpcTargetMetadata.RpcTargetMetadata() -> void StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Attribute.get -> StreamJsonRpc.JsonRpcMethodAttribute? -StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Attribute.init -> void -StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Method.get -> System.Reflection.MethodInfo! -StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Method.init -> void +StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.MethodInfo.get -> System.Reflection.MethodInfo! StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Name.get -> string! -StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Name.init -> void -StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.TargetMethodMetadata() -> void StreamJsonRpc.RpcTargetMetadata.TargetType.get -> System.Type! StreamJsonRpc.RpcTargetMetadata.TargetType.init -> void virtual StreamJsonRpc.RpcTargetMetadata.CreateEventHandlerDelegate.Invoke(StreamJsonRpc.JsonRpc! rpc, string! eventName) -> System.Delegate! diff --git a/src/StreamJsonRpc/netstandard2.0/PublicAPI.Unshipped.txt b/src/StreamJsonRpc/netstandard2.0/PublicAPI.Unshipped.txt index 432de8d70..5e68efc7d 100644 --- a/src/StreamJsonRpc/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/StreamJsonRpc/netstandard2.0/PublicAPI.Unshipped.txt @@ -12,6 +12,8 @@ static StreamJsonRpc.RpcTargetMetadata.FromClassNonPublic(System.Type! classType static StreamJsonRpc.RpcTargetMetadata.FromClassNonPublic(System.Type! classType, StreamJsonRpc.RpcTargetMetadata.ClassAndInterfaces! metadata) -> StreamJsonRpc.RpcTargetMetadata! static StreamJsonRpc.RpcTargetMetadata.FromInterface(StreamJsonRpc.RpcTargetMetadata.InterfaceCollection! interfaces) -> StreamJsonRpc.RpcTargetMetadata! static StreamJsonRpc.RpcTargetMetadata.FromInterface(System.Type! rpcContract) -> StreamJsonRpc.RpcTargetMetadata! +static StreamJsonRpc.RpcTargetMetadata.FromShape(PolyType.Abstractions.ITypeShape! shape) -> StreamJsonRpc.RpcTargetMetadata! +static StreamJsonRpc.RpcTargetMetadata.FromShape(PolyType.ITypeShapeProvider! provider) -> StreamJsonRpc.RpcTargetMetadata! static StreamJsonRpc.RpcTargetMetadata.RegisterEventArgs() -> void StreamJsonRpc.ExportRpcContractProxiesAttribute StreamJsonRpc.ExportRpcContractProxiesAttribute.ExportRpcContractProxiesAttribute() -> void @@ -117,12 +119,8 @@ StreamJsonRpc.RpcTargetMetadata.Methods.init -> void StreamJsonRpc.RpcTargetMetadata.RpcTargetMetadata() -> void StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Attribute.get -> StreamJsonRpc.JsonRpcMethodAttribute? -StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Attribute.init -> void -StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Method.get -> System.Reflection.MethodInfo! -StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Method.init -> void +StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.MethodInfo.get -> System.Reflection.MethodInfo! StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Name.get -> string! -StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Name.init -> void -StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.TargetMethodMetadata() -> void StreamJsonRpc.RpcTargetMetadata.TargetType.get -> System.Type! StreamJsonRpc.RpcTargetMetadata.TargetType.init -> void virtual StreamJsonRpc.RpcTargetMetadata.CreateEventHandlerDelegate.Invoke(StreamJsonRpc.JsonRpc! rpc, string! eventName) -> System.Delegate! diff --git a/src/StreamJsonRpc/netstandard2.1/PublicAPI.Unshipped.txt b/src/StreamJsonRpc/netstandard2.1/PublicAPI.Unshipped.txt index 432de8d70..5e68efc7d 100644 --- a/src/StreamJsonRpc/netstandard2.1/PublicAPI.Unshipped.txt +++ b/src/StreamJsonRpc/netstandard2.1/PublicAPI.Unshipped.txt @@ -12,6 +12,8 @@ static StreamJsonRpc.RpcTargetMetadata.FromClassNonPublic(System.Type! classType static StreamJsonRpc.RpcTargetMetadata.FromClassNonPublic(System.Type! classType, StreamJsonRpc.RpcTargetMetadata.ClassAndInterfaces! metadata) -> StreamJsonRpc.RpcTargetMetadata! static StreamJsonRpc.RpcTargetMetadata.FromInterface(StreamJsonRpc.RpcTargetMetadata.InterfaceCollection! interfaces) -> StreamJsonRpc.RpcTargetMetadata! static StreamJsonRpc.RpcTargetMetadata.FromInterface(System.Type! rpcContract) -> StreamJsonRpc.RpcTargetMetadata! +static StreamJsonRpc.RpcTargetMetadata.FromShape(PolyType.Abstractions.ITypeShape! shape) -> StreamJsonRpc.RpcTargetMetadata! +static StreamJsonRpc.RpcTargetMetadata.FromShape(PolyType.ITypeShapeProvider! provider) -> StreamJsonRpc.RpcTargetMetadata! static StreamJsonRpc.RpcTargetMetadata.RegisterEventArgs() -> void StreamJsonRpc.ExportRpcContractProxiesAttribute StreamJsonRpc.ExportRpcContractProxiesAttribute.ExportRpcContractProxiesAttribute() -> void @@ -117,12 +119,8 @@ StreamJsonRpc.RpcTargetMetadata.Methods.init -> void StreamJsonRpc.RpcTargetMetadata.RpcTargetMetadata() -> void StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Attribute.get -> StreamJsonRpc.JsonRpcMethodAttribute? -StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Attribute.init -> void -StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Method.get -> System.Reflection.MethodInfo! -StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Method.init -> void +StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.MethodInfo.get -> System.Reflection.MethodInfo! StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Name.get -> string! -StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.Name.init -> void -StreamJsonRpc.RpcTargetMetadata.TargetMethodMetadata.TargetMethodMetadata() -> void StreamJsonRpc.RpcTargetMetadata.TargetType.get -> System.Type! StreamJsonRpc.RpcTargetMetadata.TargetType.init -> void virtual StreamJsonRpc.RpcTargetMetadata.CreateEventHandlerDelegate.Invoke(StreamJsonRpc.JsonRpc! rpc, string! eventName) -> System.Delegate! diff --git a/test/StreamJsonRpc.Tests/RpcTargetMetadataTests.cs b/test/StreamJsonRpc.Tests/RpcTargetMetadataTests.cs index 947cf7af8..f537e228f 100644 --- a/test/StreamJsonRpc.Tests/RpcTargetMetadataTests.cs +++ b/test/StreamJsonRpc.Tests/RpcTargetMetadataTests.cs @@ -1,7 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -public class RpcTargetMetadataTests +using System.ComponentModel.DataAnnotations; +using PolyType; + +public partial class RpcTargetMetadataTests { internal interface IRpcContractBase { @@ -23,6 +26,42 @@ internal interface IRpcContractDerived : IRpcContractBase Task MethodDerivedAsync(int value); } + [GenerateShape, TypeShape(IncludeMethods = MethodShapeFlags.PublicInstance)] + internal partial interface IShapedContract + { + event EventHandler DidMath; + + Task AddAsync(int a, int b); + + [MethodShape(Ignore = true)] + void Ignored(); + + [MethodShape(Name = "Subtract")] + Task SubtractAsync(int a, int b); + + [MethodShape(Name = "Multiply"), JsonRpcMethod("Times")] + Task MultiplyAsync(int a, int b); + } + + [Fact] + public void FromShape() + { + RpcTargetMetadata metadata = RpcTargetMetadata.FromShape(Witness.ShapeProvider); + + var addAsync = Assert.Single(metadata.Methods["AddAsync"]); + var add = Assert.Single(metadata.Methods["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(4, metadata.Methods.Count); + } + [Fact] public void FromInterface_ReturnsInheritedMembers() { @@ -89,4 +128,7 @@ internal class RpcContractDerivedClass : IRpcContractDerived internal void OnDerivedEvent(MyEventArgs e) => this.DerivedEvent?.Invoke(this, e); } + + [GenerateShapeFor] + private partial class Witness; }