Files
cs/Zap.Net.Runtime/Rpc/CapabilityReflection.cs
zeekay 2aa8261f86 rebrand: complete capnp → ZAP across all C# source/build files
Finishes the in-flight capnp→ZAP rebrand on this branch. The prior commits
renamed identifiers/namespaces/files/projects (Capnp.Net.Runtime→Zap.Net.Runtime,
CapnpC→ZapC, .capnp→.zap) but left the human-readable brand name "Cap'n Proto"
throughout doc-comments, .csproj/.nuspec descriptions, README, MSBuild .xaml
rule display names, and the generated XML doc. This completes brand neutrality:

- Replace 154 "Cap'n Proto" / 4 "Cap'n-Proto" / 1 "Cap'n'p" prose occurrences
  with "ZAP" across 32 first-party files (runtime, generator, zapc-csharp,
  MSBuild integration, chocolatey nuspecs, README, docs, XML doc).
- Revert the LICENSE provenance URL to the real upstream fork
  (c80k/capnproto-dotnetcore) — upstream-fork license headers must stay.
- Canonicalize project/repo URLs to github.com/zap-proto/cs (were fabricated
  c80k/zap-dotnetcore) in csproj/nuspec/VERIFICATION/README badges.
- Canonicalize docs URLs zap-protocol(.github.io|/zap-cs|/zap-cpp-core)
  → zap-proto.dev / github.com/zap-proto/{cs,cpp-core}.
- Collapse redundant "zap \"ZAP\"" PackageTags to "zap".

Verified consistent (no toolchain on host): every .sln/.csproj ProjectReference
resolves; all namespaces are Zap*/ZapC*; MSBuild task chain
(GenerateZapFileCodeBehindTask, ZapFiles ItemGroup, _ZapcCsharp_TaskAssembly)
is uniformly renamed; props/targets/xaml/coverage/chocolatey carry no capnp.

Deliberately preserved as upstream-derived test data (not first-party source):
upstream Cap'n Proto schema fixtures (rpc.zap, rpc-twoparty.zap, json.zap,
rpc-csharp.zap), golden compiler-error outputs (errors.txt), and the
machine-generated DO-NOT-EDIT test.zap.{c++,h}. Their "Cap'n Proto" strings are
the literal output of the external schema compiler the compat test matches.
2026-06-19 23:46:57 -07:00

280 lines
12 KiB
C#

using System;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Zap.Rpc
{
/// <summary>
/// Provides functionality to construct Proxy and Skeleton instances from capability interfaces and objects implementing capability interfaces.
/// A capability interface is any .NET interface which is annotated with <see cref="ProxyAttribute"/> and <see cref="SkeletonAttribute"/>.
/// There are some intricacies to consider that you usually don't need to care about, since all that stuff will be generated.
/// </summary>
public static class CapabilityReflection
{
interface IBrokenFactory
{
System.Exception Exception { get; }
}
abstract class ProxyFactory
{
public abstract Proxy NewProxy();
}
class ProxyFactory<T>: ProxyFactory where T: Proxy, new()
{
public override Proxy NewProxy() => new T();
}
class BrokenProxyFactory: ProxyFactory, IBrokenFactory
{
readonly System.Exception _exception;
public BrokenProxyFactory(System.Exception exception)
{
_exception = exception;
}
public System.Exception Exception => _exception;
public override Proxy NewProxy()
{
throw _exception;
}
}
abstract class SkeletonFactory
{
public abstract Skeleton NewSkeleton();
}
class SkeletonFactory<T>: SkeletonFactory where T: Skeleton, new()
{
public override Skeleton NewSkeleton() => new T();
}
class BrokenSkeletonFactory: SkeletonFactory, IBrokenFactory
{
System.Exception _exception;
public BrokenSkeletonFactory(System.Exception exception)
{
_exception = exception;
}
public System.Exception Exception => _exception;
public override Skeleton NewSkeleton()
{
throw _exception;
}
}
class PolySkeletonFactory: SkeletonFactory
{
readonly SkeletonFactory[] _monoFactories;
public PolySkeletonFactory(SkeletonFactory[] monoFactories)
{
_monoFactories = monoFactories;
}
public override Skeleton NewSkeleton()
{
var poly = new PolySkeleton();
foreach (var fac in _monoFactories)
{
poly.AddInterface(fac.NewSkeleton());
}
return poly;
}
}
static ConditionalWeakTable<Type, ProxyFactory> _proxyMap =
new ConditionalWeakTable<Type, ProxyFactory>();
static ConditionalWeakTable<Type, SkeletonFactory> _skeletonMap =
new ConditionalWeakTable<Type, SkeletonFactory>();
static ConditionalWeakTable<object, Skeleton> _implMap =
new ConditionalWeakTable<object, Skeleton>();
static CapabilityReflection()
{
_proxyMap.Add(typeof(BareProxy), new ProxyFactory<BareProxy>());
_proxyMap.Add(typeof(object), new ProxyFactory<BareProxy>());
}
static SkeletonFactory CreateMonoSkeletonFactory(SkeletonAttribute attr, Type[] genericArguments)
{
var skeletonClass = attr.SkeletonClass;
if (genericArguments.Length > 0)
skeletonClass = skeletonClass.MakeGenericType(genericArguments);
return (SkeletonFactory)Activator.CreateInstance(
typeof(SkeletonFactory<>)
.MakeGenericType(skeletonClass))!;
}
static SkeletonFactory GetSkeletonFactory(Type type)
{
return _skeletonMap.GetValue(type, _ =>
{
try
{
var attrs = (from iface in _.GetInterfaces()
let generics = iface.GetGenericArguments()
from attr in iface.GetCustomAttributes(typeof(SkeletonAttribute), false)
select (Attr: (SkeletonAttribute)attr, Generics: generics)).ToArray();
if (attrs.Length == 0)
throw new InvalidCapabilityInterfaceException("No 'Skeleton' attribute defined, don't know how to create the skeleton");
if (attrs.Length == 1)
{
return CreateMonoSkeletonFactory(attrs[0].Attr, attrs[0].Generics);
}
else
{
var monoFactories = attrs.Select(a => CreateMonoSkeletonFactory(a.Attr, a.Generics)).ToArray();
return new PolySkeletonFactory(monoFactories);
}
}
catch (System.Exception exception)
{
return new BrokenSkeletonFactory(exception);
}
});
}
/// <summary>
/// Creates a Skeleton for a given interface implementation.
/// </summary>
/// <param name="obj">Interface implementation. Must implement at least one interface which is annotated with a <see cref="SkeletonAttribute"/>.</param>
/// <returns>The Skeleton</returns>
/// <exception cref="ArgumentNullException"><paramref name="obj"/> is null.</exception>
/// <exception cref="InvalidCapabilityInterfaceException">No <see cref="SkeletonAttribute"/> found on implemented interface(s).</exception>
/// <exception cref="InvalidOperationException">Mismatch between generic type arguments (if capability interface is generic).</exception>
/// <exception cref="ArgumentException">Mismatch between generic type arguments (if capability interface is generic).</exception>
/// <exception cref="System.Reflection.TargetInvocationException">Problem with instatiating the Skeleton (constructor threw exception).</exception>
/// <exception cref="MemberAccessException">Caller does not have permission to invoke the Skeleton constructor.</exception>
/// <exception cref="TypeLoadException">Problem with building the Skeleton type, or problem with loading some dependent class.</exception>
[Obsolete("Do not use this method directly. Instead, pass objects directly or use Proxy.Share<T>(). This method will be removed with next release.")]
public static Skeleton CreateSkeleton(object obj) => CreateSkeletonInternal(obj);
internal static Skeleton CreateSkeletonInternal(object obj)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
var result = _implMap.GetValue(obj, _ =>
{
var factory = GetSkeletonFactory(_.GetType());
var skeleton = factory.NewSkeleton();
skeleton.Bind(obj);
return skeleton;
});
return result;
}
static ProxyFactory GetProxyFactory(Type type)
{
return _proxyMap.GetValue(type, _ =>
{
try
{
var attrs = type
.GetCustomAttributes(typeof(ProxyAttribute), false)
.Cast<ProxyAttribute>()
.ToArray();
if (attrs.Length == 0)
throw new InvalidCapabilityInterfaceException("No 'Proxy' attribute defined, don't know how to create the proxy");
if (attrs.Length == 1)
{
Type proxyClass = attrs[0].ProxyClass;
Type[] genericArguments = type.GetGenericArguments();
if (genericArguments.Length > 0)
proxyClass = proxyClass.MakeGenericType(genericArguments);
return (ProxyFactory)Activator.CreateInstance(
typeof(ProxyFactory<>)
.MakeGenericType(proxyClass))!;
}
else
{
throw new InvalidCapabilityInterfaceException("Multiple 'Proxy' attributes defined, don't know which one to take");
}
}
catch (System.Exception exception)
{
return new BrokenProxyFactory(exception);
}
});
}
/// <summary>
/// Validates that a given type qualifies as cpapbility interface, throws <see cref="InvalidCapabilityInterfaceException"/> on failure.
/// </summary>
/// <param name="interfaceType">type to check</param>
/// <exception cref="ArgumentNullException"><paramref name="interfaceType"/> is null.</exception>
/// <exception cref="InvalidCapabilityInterfaceException">Given typ did not qualify as capability interface.
/// Message and probably InnterException give more details.</exception>
public static void ValidateCapabilityInterface(Type interfaceType)
{
if (interfaceType == null)
{
throw new ArgumentNullException(nameof(interfaceType));
}
var proxyFactory = GetProxyFactory(interfaceType);
if (proxyFactory is IBrokenFactory brokenFactory)
{
throw new InvalidCapabilityInterfaceException(
"Given type did not qualify as capability interface, see inner exception.",
brokenFactory.Exception);
}
}
/// <summary>
/// Checks whether a given type qualifies as capability interface.
/// </summary>
/// <param name="interfaceType">type to check</param>
/// <returns>true when <paramref name="interfaceType"/> is a capability interface</returns>
/// <exception cref="ArgumentNullException"><paramref name="interfaceType"/> is null.</exception>
public static bool IsValidCapabilityInterface(Type interfaceType)
{
if (interfaceType == null)
{
throw new ArgumentNullException(nameof(interfaceType));
}
var proxyFactory = GetProxyFactory(interfaceType);
return !(proxyFactory is IBrokenFactory);
}
/// <summary>
/// Constructs a Proxy for given capability interface and wraps it around given low-level capability.
/// </summary>
/// <typeparam name="TInterface">Capability interface. Must be annotated with <see cref="ProxyAttribute"/>.</typeparam>
/// <param name="cap">low-level capability</param>
/// <returns>The Proxy instance which implements <typeparamref name="TInterface"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="cap"/> is null.</exception>
/// <exception cref="InvalidCapabilityInterfaceException"><typeparamref name="TInterface"/> did not qualify as capability interface.</exception>
/// <exception cref="InvalidOperationException">Mismatch between generic type arguments (if capability interface is generic).</exception>
/// <exception cref="ArgumentException">Mismatch between generic type arguments (if capability interface is generic).</exception>
/// <exception cref="System.Reflection.TargetInvocationException">Problem with instatiating the Proxy (constructor threw exception).</exception>
/// <exception cref="MemberAccessException">Caller does not have permission to invoke the Proxy constructor.</exception>
/// <exception cref="TypeLoadException">Problem with building the Proxy type, or problem with loading some dependent class.</exception>
public static Proxy CreateProxy<TInterface>(ConsumedCapability cap)
{
var factory = GetProxyFactory(typeof(TInterface));
var proxy = factory.NewProxy();
proxy.Bind(cap);
return proxy;
}
}
}