diff --git a/src/Pipelines.Generator/Extensions/AttributeDataExtensions.cs b/src/Pipelines.Generator/Extensions/AttributeDataExtensions.cs new file mode 100644 index 0000000..63eb543 --- /dev/null +++ b/src/Pipelines.Generator/Extensions/AttributeDataExtensions.cs @@ -0,0 +1,24 @@ +namespace Microsoft.CodeAnalysis; + +/// +/// Provides extension methods for to identify pipeline-related attributes. +/// +internal static class AttributeDataExtensions +{ + /// + /// Extension methods for an instance. + /// + /// The attribute data to inspect. + extension(AttributeData? attribute) + { + /// + /// Determines whether the attribute represents a Pipelines.IgnoreAttribute with no constructor arguments. + /// + public bool IsIgnoreAttribute() => attribute is + { + AttributeClass.ContainingAssembly.Name: "Pipelines", + AttributeClass.Name: "IgnoreAttribute", + ConstructorArguments.IsDefaultOrEmpty: true, + }; + } +} diff --git a/src/Pipelines.Generator/Extensions/SourceBuilderExtensions.cs b/src/Pipelines.Generator/Extensions/SourceBuilderExtensions.cs index d3dd51c..5f93dff 100644 --- a/src/Pipelines.Generator/Extensions/SourceBuilderExtensions.cs +++ b/src/Pipelines.Generator/Extensions/SourceBuilderExtensions.cs @@ -55,7 +55,7 @@ public void NullableDisable() } /// - /// Writes a line with the generator name and version. + /// Writes a using statement for the namespace. /// public void UsingSystemCodeDomCompiler() { diff --git a/src/Pipelines.Generator/Generators/HandlesrRegistration/HandlesrRegistrationGenerator.cs b/src/Pipelines.Generator/Generators/HandlesrRegistration/HandlesrRegistrationGenerator.cs index fad97cb..638200d 100644 --- a/src/Pipelines.Generator/Generators/HandlesrRegistration/HandlesrRegistrationGenerator.cs +++ b/src/Pipelines.Generator/Generators/HandlesrRegistration/HandlesrRegistrationGenerator.cs @@ -14,12 +14,14 @@ public static void Initialize(PipelinesGeneratorContext ctx) .SyntaxProvider .CreateSyntaxProvider(Predicate, (context, ct) => Transform(context.Node, context.SemanticModel, ct)) .SelectMany((x, ct) => x) - .Collect() - .Combine(features); + .Collect(); - context.RegisterSourceOutput(handlers, static (spc, ctx) => + var toGenerate = features + .Combine(handlers); + + context.RegisterSourceOutput(toGenerate, static (spc, ctx) => { - var (handlers, features) = ctx; + var (features, handlers) = ctx; if (features is not { HasDependencyInjection: true, DisableHandlerRegistration: false }) return; @@ -50,10 +52,9 @@ private static IEnumerable Transform(SyntaxNode node, Seman if (IRequestHandler(interfaceSymbol) || IStreamRequestHandler(interfaceSymbol)) { - yield return new HandlerRegistration(node, symbol, interfaceSymbol); + yield return new HandlerRegistration(symbol, interfaceSymbol); } } - //return null; static bool ValidHandler([NotNullWhen(true)] INamedTypeSymbol? handler) => handler is { @@ -66,7 +67,7 @@ static bool ValidHandler([NotNullWhen(true)] INamedTypeSymbol? handler) => handl static bool ValidTypeArgument(ITypeSymbol typeSymbol) => typeSymbol switch { - // todo: expand failure criteria + // todo: expand failure criteria and add diagnostics INamedTypeSymbol named => named is { IsAbstract: false, @@ -101,7 +102,9 @@ static bool IStreamRequestHandler(INamedTypeSymbol handler) } } - private static void GenerateSourceOutput(SourceProductionContext spc, ImmutableArray handlersToGenerate) + private static void GenerateSourceOutput( + in SourceProductionContext spc, + in ImmutableArray handlers) { // todo: diagnostics for duplicate handlers //var descriptor = new DiagnosticDescriptor( @@ -130,9 +133,13 @@ private static void GenerateSourceOutput(SourceProductionContext spc, ImmutableA { using (sb.Block(sb, $"public static {sb.IServiceCollection} AddHandlers(this {sb.IServiceCollection} services)")) { - foreach (var handlerToGenerate in handlersToGenerate) + if (!handlers.IsDefaultOrEmpty) { - handlerToGenerate.ServiceRegistration(sb); + foreach (var handler in handlers) + { + handler.ServiceRegistration(sb); + } + sb.Line(); } sb.Line("return services;"); } diff --git a/src/Pipelines.Generator/Generators/HandlesrRegistration/Models/HandlerRegistration.cs b/src/Pipelines.Generator/Generators/HandlesrRegistration/Models/HandlerRegistration.cs index f47ceb0..604d7b7 100644 --- a/src/Pipelines.Generator/Generators/HandlesrRegistration/Models/HandlerRegistration.cs +++ b/src/Pipelines.Generator/Generators/HandlesrRegistration/Models/HandlerRegistration.cs @@ -2,19 +2,36 @@ internal readonly record struct HandlerRegistration { + private readonly bool Disabled; + public readonly string Handler; + public readonly string Interface; - public readonly SyntaxNode Node; - public HandlerRegistration(SyntaxNode node, INamedTypeSymbol classSymbol, INamedTypeSymbol interfaceSymbol) + public HandlerRegistration(INamedTypeSymbol classSymbol, INamedTypeSymbol interfaceSymbol) { + var attributes = classSymbol.GetAttributes(); + + Disabled = attributes.Any(x => x.IsIgnoreAttribute()); Handler = classSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); Interface = interfaceSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - Node = node; } public void ServiceRegistration(SourceBuilder sb) { + if (Disabled) return; + sb.Line(sb, $"services.AddScoped<{Interface}, {Handler}>();"); } + + public bool Equals(HandlerRegistration other) + { + return other.Handler == Handler + && other.Interface == Interface; + } + + public override int GetHashCode() + { + return HashCode.Combine(Handler, Interface); + } } diff --git a/src/Pipelines.Generator/Primitives/EquatableArray.cs b/src/Pipelines.Generator/Primitives/EquatableArray.cs new file mode 100644 index 0000000..c8da60a --- /dev/null +++ b/src/Pipelines.Generator/Primitives/EquatableArray.cs @@ -0,0 +1,134 @@ +using System.Runtime.CompilerServices; + +namespace System.Collections.Generic; + +/// +/// Provides extension methods for creating instances. +/// +internal static class EquatableArray +{ + /// + /// Converts an to an . + /// + /// The type of elements in the collection. + /// The collection of values to convert. + /// An containing the values from the input collection. + public static EquatableArray ToEquatableArray(this IEnumerable values) where T : IEquatable + { + return new EquatableArray(values); + } +} + +/// +/// An immutable, equatable array. This is equivalent to but with value equality support. +/// +/// The type of values in the array. +internal readonly struct EquatableArray : IEquatable> + where T : IEquatable +{ + public static EquatableArray Empty { get; } = new(); + + /// + /// The underlying array. + /// + private readonly ImmutableArray _array; + + public int Length => _array.Length; + + /// + /// Creates a new instance. + /// + public EquatableArray() + { + _array = []; + } + + /// + /// Creates a new instance. + /// + /// The input to wrap. + public EquatableArray(T[] array) + { + _array = [.. array]; + } + + /// + /// Creates a new instance. + /// + /// The input to wrap. + public EquatableArray(IEnumerable values) + { + _array = [.. values]; + } + + /// + public bool Equals(EquatableArray array) + { + return AsSpan().SequenceEqual(array.AsSpan()); + } + + /// + public override bool Equals(object? obj) + { + return obj is EquatableArray array && this.Equals(array); + } + + /// + public override int GetHashCode() + { + if (_array.IsDefaultOrEmpty) + { + return 0; + } + + HashCode hashCode = default; + + foreach (T item in _array) + { + hashCode.Add(item); + } + + return hashCode.ToHashCode(); + } + + /// + /// Returns a wrapping the current items. + /// + /// A wrapping the current items. + public ReadOnlySpan AsSpan() + { + return _array.AsSpan(); + } + + /// + /// Returns an enumerator for the contents of the array. + /// + /// An enumerator. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImmutableArray.Enumerator GetEnumerator() + { + return _array.GetEnumerator(); + } + + /// + /// Checks whether two values are the same. + /// + /// The first value. + /// The second value. + /// Whether and are equal. + public static bool operator ==(EquatableArray left, EquatableArray right) + { + return left.Equals(right); + } + + /// + /// Checks whether two values are not the same. + /// + /// The first value. + /// The second value. + /// Whether and are not equal. + public static bool operator !=(EquatableArray left, EquatableArray right) + { + return !left.Equals(right); + } +} \ No newline at end of file diff --git a/src/Pipelines.Generator/Primitives/HashCode.cs b/src/Pipelines.Generator/Primitives/HashCode.cs new file mode 100644 index 0000000..33a5280 --- /dev/null +++ b/src/Pipelines.Generator/Primitives/HashCode.cs @@ -0,0 +1,383 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace System; + +/// +/// Polyfill for .NET 6 HashCode +/// +internal struct HashCode +{ + private static readonly uint s_seed = GenerateGlobalSeed(); + + private const uint Prime1 = 2654435761U; + private const uint Prime2 = 2246822519U; + private const uint Prime3 = 3266489917U; + private const uint Prime4 = 668265263U; + private const uint Prime5 = 374761393U; + + private uint _v1, _v2, _v3, _v4; + private uint _queue1, _queue2, _queue3; + private uint _length; + + private static uint GenerateGlobalSeed() + { + var buffer = new byte[sizeof(uint)]; +#pragma warning disable RS1035 // Do not use APIs banned for analyzers + new Random().NextBytes(buffer); +#pragma warning restore RS1035 // Do not use APIs banned for analyzers + return BitConverter.ToUInt32(buffer, 0); + } + + public static int Combine(T1 value1) + { + // Provide a way of diffusing bits from something with a limited + // input hash space. For example, many enums only have a few + // possible hashes, only using the bottom few bits of the code. Some + // collections are built on the assumption that hashes are spread + // over a larger space, so diffusing the bits may help the + // collection work more efficiently. + + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + + uint hash = MixEmptyState(); + hash += 4; + + hash = QueueRound(hash, hc1); + + hash = MixFinal(hash); + return (int)hash; + } + + public static int Combine(T1 value1, T2 value2) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + + uint hash = MixEmptyState(); + hash += 8; + + hash = QueueRound(hash, hc1); + hash = QueueRound(hash, hc2); + + hash = MixFinal(hash); + return (int)hash; + } + + public static int Combine(T1 value1, T2 value2, T3 value3) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + + uint hash = MixEmptyState(); + hash += 12; + + hash = QueueRound(hash, hc1); + hash = QueueRound(hash, hc2); + hash = QueueRound(hash, hc3); + + hash = MixFinal(hash); + return (int)hash; + } + + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint hash = MixState(v1, v2, v3, v4); + hash += 16; + + hash = MixFinal(hash); + return (int)hash; + } + + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + uint hc5 = (uint)(value5?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint hash = MixState(v1, v2, v3, v4); + hash += 20; + + hash = QueueRound(hash, hc5); + + hash = MixFinal(hash); + return (int)hash; + } + + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + uint hc5 = (uint)(value5?.GetHashCode() ?? 0); + uint hc6 = (uint)(value6?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint hash = MixState(v1, v2, v3, v4); + hash += 24; + + hash = QueueRound(hash, hc5); + hash = QueueRound(hash, hc6); + + hash = MixFinal(hash); + return (int)hash; + } + + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + uint hc5 = (uint)(value5?.GetHashCode() ?? 0); + uint hc6 = (uint)(value6?.GetHashCode() ?? 0); + uint hc7 = (uint)(value7?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint hash = MixState(v1, v2, v3, v4); + hash += 28; + + hash = QueueRound(hash, hc5); + hash = QueueRound(hash, hc6); + hash = QueueRound(hash, hc7); + + hash = MixFinal(hash); + return (int)hash; + } + + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + uint hc5 = (uint)(value5?.GetHashCode() ?? 0); + uint hc6 = (uint)(value6?.GetHashCode() ?? 0); + uint hc7 = (uint)(value7?.GetHashCode() ?? 0); + uint hc8 = (uint)(value8?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + v1 = Round(v1, hc5); + v2 = Round(v2, hc6); + v3 = Round(v3, hc7); + v4 = Round(v4, hc8); + + uint hash = MixState(v1, v2, v3, v4); + hash += 32; + + hash = MixFinal(hash); + return (int)hash; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4) + { + v1 = s_seed + Prime1 + Prime2; + v2 = s_seed + Prime2; + v3 = s_seed; + v4 = s_seed - Prime1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Round(uint hash, uint input) + { + return RotateLeft(hash + input * Prime2, 13) * Prime1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint QueueRound(uint hash, uint queuedValue) + { + return RotateLeft(hash + queuedValue * Prime3, 17) * Prime4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixState(uint v1, uint v2, uint v3, uint v4) + { + return RotateLeft(v1, 1) + RotateLeft(v2, 7) + RotateLeft(v3, 12) + RotateLeft(v4, 18); + } + + private static uint MixEmptyState() + { + return s_seed + Prime5; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixFinal(uint hash) + { + hash ^= hash >> 15; + hash *= Prime2; + hash ^= hash >> 13; + hash *= Prime3; + hash ^= hash >> 16; + return hash; + } + + public void Add(T value) + { + Add(value?.GetHashCode() ?? 0); + } + + public void Add(T value, IEqualityComparer? comparer) + { + Add(value is null ? 0 : (comparer?.GetHashCode(value) ?? value.GetHashCode())); + } + + private void Add(int value) + { + // The original xxHash works as follows: + // 0. Initialize immediately. We can't do this in a struct (no + // default ctor). + // 1. Accumulate blocks of length 16 (4 uints) into 4 accumulators. + // 2. Accumulate remaining blocks of length 4 (1 uint) into the + // hash. + // 3. Accumulate remaining blocks of length 1 into the hash. + + // There is no need for #3 as this type only accepts ints. _queue1, + // _queue2 and _queue3 are basically a buffer so that when + // ToHashCode is called we can execute #2 correctly. + + // We need to initialize the xxHash32 state (_v1 to _v4) lazily (see + // #0) nd the last place that can be done if you look at the + // original code is just before the first block of 16 bytes is mixed + // in. The xxHash32 state is never used for streams containing fewer + // than 16 bytes. + + // To see what's really going on here, have a look at the Combine + // methods. + + uint val = (uint)value; + + // Storing the value of _length locally shaves of quite a few bytes + // in the resulting machine code. + uint previousLength = _length++; + uint position = previousLength % 4; + + // Switch can't be inlined. + + if (position == 0) + _queue1 = val; + else if (position == 1) + _queue2 = val; + else if (position == 2) + _queue3 = val; + else // position == 3 + { + if (previousLength == 3) + Initialize(out _v1, out _v2, out _v3, out _v4); + + _v1 = Round(_v1, _queue1); + _v2 = Round(_v2, _queue2); + _v3 = Round(_v3, _queue3); + _v4 = Round(_v4, val); + } + } + + public int ToHashCode() + { + // Storing the value of _length locally shaves of quite a few bytes + // in the resulting machine code. + uint length = _length; + + // position refers to the *next* queue position in this method, so + // position == 1 means that _queue1 is populated; _queue2 would have + // been populated on the next call to Add. + uint position = length % 4; + + // If the length is less than 4, _v1 to _v4 don't contain anything + // yet. xxHash32 treats this differently. + + uint hash = length < 4 ? MixEmptyState() : MixState(_v1, _v2, _v3, _v4); + + // _length is incremented once per Add(Int32) and is therefore 4 + // times too small (xxHash length is in bytes, not ints). + + hash += length * 4; + + // Mix what remains in the queue + + // Switch can't be inlined right now, so use as few branches as + // possible by manually excluding impossible scenarios (position > 1 + // is always false if position is not > 0). + if (position > 0) + { + hash = QueueRound(hash, _queue1); + if (position > 1) + { + hash = QueueRound(hash, _queue2); + if (position > 2) + hash = QueueRound(hash, _queue3); + } + } + + hash = MixFinal(hash); + return (int)hash; + } + +#pragma warning disable 0809 + // Obsolete member 'memberA' overrides non-obsolete member 'memberB'. + // Disallowing GetHashCode and Equals is by design + + // * We decided to not override GetHashCode() to produce the hash code + // as this would be weird, both naming-wise as well as from a + // behavioral standpoint (GetHashCode() should return the object's + // hash code, not the one being computed). + + // * Even though ToHashCode() can be called safely multiple times on + // this implementation, it is not part of the contract. If the + // implementation has to change in the future we don't want to worry + // about people who might have incorrectly used this type. + + [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => throw new NotSupportedException("Hash code not supported"); + + [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) => throw new NotSupportedException("Equality not supported"); +#pragma warning restore 0809 + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint RotateLeft(uint value, int offset) + => (value << offset) | (value >> (32 - offset)); +} \ No newline at end of file diff --git a/src/Pipelines/Attributes/IgnoreAttribute.cs b/src/Pipelines/Attributes/IgnoreAttribute.cs new file mode 100644 index 0000000..1b706e9 --- /dev/null +++ b/src/Pipelines/Attributes/IgnoreAttribute.cs @@ -0,0 +1,15 @@ +namespace Pipelines.Attributes; + +/// +/// Marks that a handler should be ignored and not registered to di automatically. +/// +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +public sealed class IgnoreAttribute : Attribute +{ + /// + /// Initializes a new instance of the class. + /// + public IgnoreAttribute() + { + } +} diff --git a/test/Pipelines.Generator.Test.Integration/IgnoreAttributeTests.cs b/test/Pipelines.Generator.Test.Integration/IgnoreAttributeTests.cs new file mode 100644 index 0000000..8d6f944 --- /dev/null +++ b/test/Pipelines.Generator.Test.Integration/IgnoreAttributeTests.cs @@ -0,0 +1,51 @@ +using Pipelines.Attributes; +using Pipelines.Requests; + +namespace Pipelines.Generator.Test.Integration; + +public sealed class IgnoreAttributeTests : TestBase +{ + [Test] + public async Task Should_Not_Register_Handler() + { + var services = SetupServices(); + + var pingHandler = services.GetService>(); + await That(pingHandler).IsNull(); + + var pongHandler = services.GetService>(); + await That(pongHandler).IsNull(); + } + + [Test] + public async Task Should_Not_Resolve_Pipeline() + { + var services = SetupServices(); + + var pingPipline = () => services.GetService>(); + await That(pingPipline).Throws().WithMessageContaining("Unable to resolve service"); + + var pongPipline = () => services.GetService>(); + await That(pongPipline).Throws().WithMessageContaining("Unable to resolve service"); ; + } + + public sealed class Ping : IRequest; + + public sealed record Pong(int Num) : IRequest; + + [Ignore] + public sealed class PingHandler(RequestContext context) : IRequestHandler, IRequestHandler + { + public ValueTask Handle(Ping request, CancellationToken cancellationToken = default) + { + context.CallStack.Push(GetType().Name); + return new(new Pong(100)); + } + + public ValueTask Handle(Pong request, CancellationToken cancellationToken = default) + { + context.CallStack.Push(GetType().Name); + return new(new Ping()); + } + } +} diff --git a/test/Pipelines.Generator.Test/HandlersRegistrationTests.cs b/test/Pipelines.Generator.Test/HandlersRegistrationTests.cs index fbf0360..a726331 100644 --- a/test/Pipelines.Generator.Test/HandlersRegistrationTests.cs +++ b/test/Pipelines.Generator.Test/HandlersRegistrationTests.cs @@ -73,37 +73,61 @@ public Task NoServiceCollectionAvailable() [Test] public Task PrivateHandler() { - return Verify(configure); + return Verify(context => + { + configure(context); + context.IgnoreCompilationErrors = true; + }); } [Test] public Task PrivateRequest() { - return Verify(configure); + return Verify(context => + { + configure(context); + context.IgnoreCompilationErrors = true; + }); } [Test] public Task PrivateResponse() { - return Verify(configure); + return Verify(context => + { + configure(context); + context.IgnoreCompilationErrors = true; + }); } [Test] public Task ProtectedHandler() { - return Verify(configure); + return Verify(context => + { + configure(context); + context.IgnoreCompilationErrors = true; + }); } [Test] public Task ProtectedRequest() { - return Verify(configure); + return Verify(context => + { + configure(context); + context.IgnoreCompilationErrors = true; + }); } [Test] public Task ProtectedResponse() { - return Verify(configure); + return Verify(context => + { + configure(context); + context.IgnoreCompilationErrors = true; + }); } [Test] @@ -118,6 +142,16 @@ public Task RequestWithoutContract() return Verify(configure); } + [Test] + public Task ServiceCollectionMethod() + { + return Verify(context => + { + configure(context); + context.IgnoreCompilationErrors = true; + }); + } + [Test] public Task SimpleHandler() { diff --git a/test/Pipelines.Generator.Test/IgnoreAttributeTests.cs b/test/Pipelines.Generator.Test/IgnoreAttributeTests.cs new file mode 100644 index 0000000..a99c37f --- /dev/null +++ b/test/Pipelines.Generator.Test/IgnoreAttributeTests.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; + +namespace Pipelines.Generator.Test; + +public sealed class IgnoreAttributeTests : TestBase +{ + [Test] + public Task SimpleHandler() + { + return Verify(); + } +} diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/AbstractHandler/Program.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/AbstractHandler/Program.cs index 056d622..c0be0be 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/AbstractHandler/Program.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/AbstractHandler/Program.cs @@ -11,8 +11,10 @@ public static async Task Main() public sealed class Result; public abstract record Command : IRequest> + where TSelf : Command where TResult : notnull; + public abstract class CommandHandler : IRequestHandler> where TCommand : Command @@ -21,6 +23,14 @@ public abstract class CommandHandler : public abstract ValueTask> Handle(TCommand command, CancellationToken cancellationToken); } +public abstract class StreamCommandHandler : + IStreamRequestHandler> + where TCommand : Command + where TResult : notnull +{ + public abstract IAsyncEnumerable> Handle(TCommand command, CancellationToken cancellationToken); +} + // Define class from convention public sealed record Pong; @@ -34,3 +44,11 @@ public override ValueTask> Handle(Ping Ping, CancellationToken canc return new(r); } } + +public sealed class PingStreamCommandHandler : StreamCommandHandler +{ + public override async IAsyncEnumerable> Handle(Ping Ping, CancellationToken cancellationToken) + { + yield break; + } +} diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/AbstractHandler/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/AbstractHandler/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs index bd220fd..c86231d 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/AbstractHandler/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/AbstractHandler/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs @@ -15,6 +15,8 @@ internal static partial class PipelinesRegistrations public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddHandlers(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services) { services.AddScoped>, global::Pipelines.Test.Integration.PingCommandHandler>(); + services.AddScoped>, global::Pipelines.Test.Integration.PingStreamCommandHandler>(); + return services; } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/DeepNamespace/Program.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/DeepNamespace/Program.cs index 1072717..1e187f7 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/DeepNamespace/Program.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/DeepNamespace/Program.cs @@ -11,4 +11,12 @@ public ValueTask Handle(Ping request, CancellationToken cancellationToken) return new ValueTask(new Pong(request.Id)); } } + + public sealed class PingStreamHandler : IStreamRequestHandler + { + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } + } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/DeepNamespace/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/DeepNamespace/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs index 7652b9f..5ab2864 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/DeepNamespace/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/DeepNamespace/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs @@ -15,6 +15,8 @@ internal static partial class PipelinesRegistrations public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddHandlers(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services) { services.AddScoped, global::Some.Very.Very.Very.Very.Deep.Namespace.ThatIUseToTestTheSourceGenSoThatItCanHandleLotsOfDifferentInput.PingHandler>(); + services.AddScoped, global::Some.Very.Very.Very.Very.Deep.Namespace.ThatIUseToTestTheSourceGenSoThatItCanHandleLotsOfDifferentInput.PingStreamHandler>(); + return services; } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/DuplicateHandlers/Program.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/DuplicateHandlers/Program.cs index 9441035..0ce436d 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/DuplicateHandlers/Program.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/DuplicateHandlers/Program.cs @@ -17,3 +17,19 @@ public ValueTask Handle(Ping request, CancellationToken cancellationToken) return new ValueTask(new Pong(request.Id)); } } + +public sealed class PingStreamHandler : IStreamRequestHandler +{ + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } +} + +public sealed class DuplicateStreamPingHandler : IStreamRequestHandler +{ + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } +} diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/DuplicateHandlers/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/DuplicateHandlers/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs index 3c54953..29891fc 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/DuplicateHandlers/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/DuplicateHandlers/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs @@ -16,6 +16,9 @@ internal static partial class PipelinesRegistrations { services.AddScoped, global::PingHandler>(); services.AddScoped, global::DuplicatePingHandler>(); + services.AddScoped, global::PingStreamHandler>(); + services.AddScoped, global::DuplicateStreamPingHandler>(); + return services; } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/FileHandler/Program.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/FileHandler/Program.cs index 9494ebc..d075529 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/FileHandler/Program.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/FileHandler/Program.cs @@ -11,4 +11,12 @@ public ValueTask Handle(Ping request, CancellationToken cancellationToken) return new ValueTask(new Pong(request.Id)); } } + + file class PingStreamHandler : IStreamRequestHandler + { + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } + } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/GenericHandler/Program.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/GenericHandler/Program.cs index f11e089..e3b8f07 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/GenericHandler/Program.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/GenericHandler/Program.cs @@ -4,9 +4,10 @@ namespace Pipelines.Test.Integration; public sealed class Result; public abstract record Command : IRequest> + where TSelf : Command where TResult : notnull; -public class CommandHandler : +public abstract class CommandHandler : IRequestHandler> where TCommand : Command where TResult : notnull @@ -14,6 +15,14 @@ public class CommandHandler : public abstract ValueTask> Handle(TCommand command, CancellationToken cancellationToken); } +public abstract class StreamCommandHandler : + IStreamRequestHandler> + where TCommand : Command + where TResult : notnull +{ + public abstract IAsyncEnumerable> Handle(TCommand command, CancellationToken cancellationToken); +} + public sealed record Pong; public sealed record Ping : Command; @@ -26,3 +35,11 @@ public override ValueTask> Handle(Ping Ping, CancellationToken canc return new(r); } } + +public sealed class PingStreamCommandHandler : StreamCommandHandler +{ + public override async IAsyncEnumerable> Handle(Ping Ping, CancellationToken cancellationToken) + { + yield break; + } +} diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/GenericHandler/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/GenericHandler/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs index bd220fd..c86231d 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/GenericHandler/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/GenericHandler/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs @@ -15,6 +15,8 @@ internal static partial class PipelinesRegistrations public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddHandlers(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services) { services.AddScoped>, global::Pipelines.Test.Integration.PingCommandHandler>(); + services.AddScoped>, global::Pipelines.Test.Integration.PingStreamCommandHandler>(); + return services; } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/InsideClass/Program.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/InsideClass/Program.cs index aa91074..525d2ae 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/InsideClass/Program.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/InsideClass/Program.cs @@ -14,4 +14,12 @@ public ValueTask Handle(Ping request, CancellationToken cancellationToken) return new(Pong); } } + + public sealed class StreamPingHandler : IStreamRequestHandler + { + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } + } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/InsideClass/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/InsideClass/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs index 06ea9e7..50149da 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/InsideClass/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/InsideClass/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs @@ -15,6 +15,8 @@ internal static partial class PipelinesRegistrations public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddHandlers(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services) { services.AddScoped, global::Pipelines.Test.Integration.InsideClass.PingHandler>(); + services.AddScoped, global::Pipelines.Test.Integration.InsideClass.StreamPingHandler>(); + return services; } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/MultipleInterfaceImpl/Program.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/MultipleInterfaceImpl/Program.cs index fd5fbe6..6d0709e 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/MultipleInterfaceImpl/Program.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/MultipleInterfaceImpl/Program.cs @@ -7,7 +7,7 @@ public sealed record Ping2(); public sealed record Pong(); public class Ping1Handler : - IRequestHandler + IRequestHandler, IRequestHandler { public ValueTask Handle(Ping1 request, CancellationToken cancellationToken) @@ -20,3 +20,18 @@ public ValueTask Handle(Ping2 request, CancellationToken cancellationToken return new ValueTask(new Pong()); } } + +public class StreamPing1Handler : + IStreamRequestHandler, + IStreamRequestHandler +{ + public async IAsyncEnumerable Handle(Ping1 request, CancellationToken cancellationToken) + { + yield break; + } + + public async IAsyncEnumerable Handle(Ping2 request, CancellationToken cancellationToken) + { + yield break; + } +} diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/MultipleInterfaceImpl/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/MultipleInterfaceImpl/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs index e467c70..5c37f5b 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/MultipleInterfaceImpl/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/MultipleInterfaceImpl/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs @@ -16,6 +16,9 @@ internal static partial class PipelinesRegistrations { services.AddScoped, global::SomeProgram.Ping1Handler>(); services.AddScoped, global::SomeProgram.Ping1Handler>(); + services.AddScoped, global::SomeProgram.StreamPing1Handler>(); + services.AddScoped, global::SomeProgram.StreamPing1Handler>(); + return services; } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/PrivateHandler/Program.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/PrivateHandler/Program.cs index 47b95f9..ad8256e 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/PrivateHandler/Program.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/PrivateHandler/Program.cs @@ -17,5 +17,13 @@ public ValueTask Handle(Ping request, CancellationToken cancellationToken) return new ValueTask(new Pong(request.Id)); } } + + private class StreamPingHandler : IStreamRequestHandler + { + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } + } } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/PrivateRequest/Program.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/PrivateRequest/Program.cs index d0505de..d85fdd6 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/PrivateRequest/Program.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/PrivateRequest/Program.cs @@ -2,7 +2,7 @@ namespace Some.Nested.Types { public static class Program { - public static void Task Main() + public static void Main() { } @@ -17,5 +17,13 @@ public ValueTask Handle(Ping request, CancellationToken cancellationToken) return new ValueTask(new Pong(request.Id)); } } + + public class PingStreamHandler : IStreamRequestHandler + { + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } + } } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/PrivateResponse/Program.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/PrivateResponse/Program.cs index 142303f..7b4d16e 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/PrivateResponse/Program.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/PrivateResponse/Program.cs @@ -2,7 +2,7 @@ namespace Some.Nested.Types { public static class Program { - public static void Task Main() + public static void Main() { } @@ -17,5 +17,13 @@ public ValueTask Handle(Ping request, CancellationToken cancellationToken) return new ValueTask(new Pong(request.Id)); } } + + public class PingStreamHandler : IStreamRequestHandler + { + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } + } } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/ProtectedHandler/Program.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/ProtectedHandler/Program.cs index ce80062..02bc22e 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/ProtectedHandler/Program.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/ProtectedHandler/Program.cs @@ -2,7 +2,7 @@ namespace Some.Nested.Types { public static class Program { - public static void Task Main() + public static void Main() { } @@ -17,5 +17,13 @@ public ValueTask Handle(Ping request, CancellationToken cancellationToken) return new ValueTask(new Pong(request.Id)); } } + + protected class PingStreamHandler : IStreamRequestHandler + { + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } + } } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/ProtectedRequest/Program.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/ProtectedRequest/Program.cs index 82e0c50..e59bf7c 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/ProtectedRequest/Program.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/ProtectedRequest/Program.cs @@ -2,7 +2,7 @@ namespace Some.Nested.Types { public static class Program { - public static void Task Main() + public static void Main() { } @@ -17,5 +17,13 @@ public ValueTask Handle(Ping request, CancellationToken cancellationToken) return new ValueTask(new Pong(request.Id)); } } + + public class PingStreamHandler : IStreamRequestHandler + { + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } + } } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/ProtectedResponse/Program.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/ProtectedResponse/Program.cs index fc02a1f..2717a3e 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/ProtectedResponse/Program.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/ProtectedResponse/Program.cs @@ -2,7 +2,7 @@ namespace Some.Nested.Types { public static class Program { - public static void Task Main() + public static void Main() { } @@ -17,5 +17,13 @@ public ValueTask Handle(Ping request, CancellationToken cancellationToken) return new ValueTask(new Pong(request.Id)); } } + + public class PingStreamHandler : IStreamRequestHandler + { + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } + } } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/RequestWithContract/Program.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/RequestWithContract/Program.cs index aa91074..82a89ca 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/RequestWithContract/Program.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/RequestWithContract/Program.cs @@ -2,7 +2,7 @@ public class InsideClass { - public sealed class Ping : IRequest; + public sealed class Ping : IRequest, IStreamRequest; public sealed class Pong; @@ -14,4 +14,12 @@ public ValueTask Handle(Ping request, CancellationToken cancellationToken) return new(Pong); } } + + public class PingStreamHandler : IStreamRequestHandler + { + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } + } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/RequestWithContract/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/RequestWithContract/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs index 06ea9e7..5925b52 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/RequestWithContract/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/RequestWithContract/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs @@ -15,6 +15,8 @@ internal static partial class PipelinesRegistrations public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddHandlers(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services) { services.AddScoped, global::Pipelines.Test.Integration.InsideClass.PingHandler>(); + services.AddScoped, global::Pipelines.Test.Integration.InsideClass.PingStreamHandler>(); + return services; } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/RequestWithoutContract/Program.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/RequestWithoutContract/Program.cs index 9ba4543..31b9999 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/RequestWithoutContract/Program.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/RequestWithoutContract/Program.cs @@ -14,4 +14,12 @@ public ValueTask Handle(Ping request, CancellationToken cancellationToken) return new(Pong); } } + + public class PingStreamHandler : IStreamRequestHandler + { + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } + } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/RequestWithoutContract/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/RequestWithoutContract/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs index 06ea9e7..5925b52 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/RequestWithoutContract/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/RequestWithoutContract/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs @@ -15,6 +15,8 @@ internal static partial class PipelinesRegistrations public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddHandlers(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services) { services.AddScoped, global::Pipelines.Test.Integration.InsideClass.PingHandler>(); + services.AddScoped, global::Pipelines.Test.Integration.InsideClass.PingStreamHandler>(); + return services; } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/ServiceCollectionMethod/Program.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/ServiceCollectionMethod/Program.cs new file mode 100644 index 0000000..216a9c1 --- /dev/null +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/ServiceCollectionMethod/Program.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Some; + +public static class Program +{ + public static async Task Main() + { + var services = new ServiceCollection(); + + services.AddHandlers(); + + var serviceProvider = services.BuildServiceProvider(); + + var pipeline = serviceProvider.GetRequiredService(); + + _ = await pipeline.Send(new Ping(Guid.NewGuid())); + } + + public sealed record Ping(Guid Id) : IRequest, IStreamRequest; + + public sealed class PingHandler : IRequestHandler + { + public ValueTask Handle(Ping request, CancellationToken cancellationToken) + { + var bytes = request.Id.ToByteArray(); + return new ValueTask(bytes); + } + } + + public class PingStreamHandler : IStreamRequestHandler + { + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } + } +} diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/ServiceCollectionMethod/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/ServiceCollectionMethod/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs new file mode 100644 index 0000000..7bf659c --- /dev/null +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/ServiceCollectionMethod/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs @@ -0,0 +1,24 @@ + +#nullable enable + +using global::System.CodeDom.Compiler; +using global::Microsoft.Extensions.DependencyInjection; +using global::Microsoft.Extensions.DependencyInjection.Extensions; +using global::Pipelines; +using global::Pipelines.Requests; +using global::Pipelines.Streams; + +namespace Microsoft.Extensions.DependencyInjection; + +internal static partial class PipelinesRegistrations +{ + public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddHandlers(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services) + { + services.AddScoped, global::Some.Program.PingHandler>(); + services.AddScoped, global::Some.Program.PingStreamHandler>(); + + return services; + } +} + +#nullable disable diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/SimpleHandler/Program.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/SimpleHandler/Program.cs index 3f7ef93..c107732 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/SimpleHandler/Program.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/SimpleHandler/Program.cs @@ -11,4 +11,12 @@ public ValueTask Handle(Ping request, CancellationToken cancellationToken) return new ValueTask(new Pong(request.Id)); } } + + public class PingStreamHandler : IStreamRequestHandler + { + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } + } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/SimpleHandler/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/SimpleHandler/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs index 4308f8a..6f8e4d4 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/SimpleHandler/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/SimpleHandler/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs @@ -15,6 +15,8 @@ internal static partial class PipelinesRegistrations public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddHandlers(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services) { services.AddScoped, global::Some.Nested.Types.PingHandler>(); + services.AddScoped, global::Some.Nested.Types.PingStreamHandler>(); + return services; } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/StructHandler/Program.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/StructHandler/Program.cs index bba405d..f440a39 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/StructHandler/Program.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/StructHandler/Program.cs @@ -1,4 +1,4 @@ -public sealed record Ping(Guid Id) : IRequest; +public sealed record Ping(Guid Id) : IRequest, IStreamRequest; public sealed record Pong(Guid Id); @@ -9,3 +9,11 @@ public ValueTask Handle(Ping request, CancellationToken cancellationToken) return new ValueTask(new Pong(request.Id)); } } + +public readonly struct PingStreamHandler : IStreamRequestHandler +{ + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } +} diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/TwoHandlersDifferentNamespace/Program.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/TwoHandlersDifferentNamespace/Program.cs index 7c78937..82741e9 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/TwoHandlersDifferentNamespace/Program.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/TwoHandlersDifferentNamespace/Program.cs @@ -1,22 +1,6 @@ namespace Some.Nested.Types.One { - public static class Program - { - public static async Task Main() - { - var services = new ServiceCollection(); - - services.AddBlackwing(); - - var serviceProvider = services.BuildServiceProvider(); - - var mediator = serviceProvider.GetRequiredService(); - - _ = await mediator.Send(new Ping(Guid.NewGuid())); - } - } - - public sealed record Ping(Guid Id) : IRequest; + public sealed record Ping(Guid Id) : IRequest, IStreamRequest; public sealed class PingHandler : IRequestHandler { @@ -26,11 +10,19 @@ public ValueTask Handle(Ping request, CancellationToken cancellationToke return new ValueTask(bytes); } } + + public class PingStreamHandler : IStreamRequestHandler + { + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } + } } namespace Some.Nested.Types.Two { - public sealed record Ping(Guid Id) : IRequest; + public sealed record Ping(Guid Id) : IRequest, IStreamRequest; public sealed class PingHandler : IRequestHandler { @@ -40,4 +32,12 @@ public ValueTask Handle(Ping request, CancellationToken cancellationToke return new ValueTask(bytes); } } + + public class PingStreamHandler : IStreamRequestHandler + { + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } + } } diff --git a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/TwoHandlersDifferentNamespace/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/TwoHandlersDifferentNamespace/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs index 951133e..46e39a1 100644 --- a/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/TwoHandlersDifferentNamespace/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs +++ b/test/Pipelines.Generator.Test/Resources/HandlersRegistrationTests/TwoHandlersDifferentNamespace/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs @@ -15,7 +15,10 @@ internal static partial class PipelinesRegistrations public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddHandlers(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services) { services.AddScoped, global::Some.Nested.Types.One.PingHandler>(); + services.AddScoped, global::Some.Nested.Types.One.PingStreamHandler>(); services.AddScoped, global::Some.Nested.Types.Two.PingHandler>(); + services.AddScoped, global::Some.Nested.Types.Two.PingStreamHandler>(); + return services; } } diff --git a/test/Pipelines.Generator.Test/Resources/IgnoreAttributeTests/SimpleHandler/Program.cs b/test/Pipelines.Generator.Test/Resources/IgnoreAttributeTests/SimpleHandler/Program.cs new file mode 100644 index 0000000..96935cb --- /dev/null +++ b/test/Pipelines.Generator.Test/Resources/IgnoreAttributeTests/SimpleHandler/Program.cs @@ -0,0 +1,39 @@ +namespace Simple; + +public sealed record Ping(Guid Id); + +public sealed record Pong(Guid Id); + +public class PingHandler : IRequestHandler +{ + public ValueTask Handle(Ping request, CancellationToken cancellationToken) + { + return new ValueTask(new Pong(request.Id)); + } +} + +[Ignore] +public class PingHandlerIgnore : IRequestHandler +{ + public ValueTask Handle(Ping request, CancellationToken cancellationToken) + { + return new ValueTask(new Pong(request.Id)); + } +} + +public class PingStream : IStreamRequestHandler +{ + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } +} + +[Ignore] +public class PingStreamIgnore : IStreamRequestHandler +{ + public async IAsyncEnumerable Handle(Ping request, CancellationToken cancellationToken) + { + yield break; + } +} diff --git a/test/Pipelines.Generator.Test/Resources/IgnoreAttributeTests/SimpleHandler/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs b/test/Pipelines.Generator.Test/Resources/IgnoreAttributeTests/SimpleHandler/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs new file mode 100644 index 0000000..b132807 --- /dev/null +++ b/test/Pipelines.Generator.Test/Resources/IgnoreAttributeTests/SimpleHandler/Snapshot#PipelinesRegistrations.Handlers.g.verified.cs @@ -0,0 +1,24 @@ + +#nullable enable + +using global::System.CodeDom.Compiler; +using global::Microsoft.Extensions.DependencyInjection; +using global::Microsoft.Extensions.DependencyInjection.Extensions; +using global::Pipelines; +using global::Pipelines.Requests; +using global::Pipelines.Streams; + +namespace Microsoft.Extensions.DependencyInjection; + +internal static partial class PipelinesRegistrations +{ + public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddHandlers(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services) + { + services.AddScoped, global::Simple.PingHandler>(); + services.AddScoped, global::Simple.PingStream>(); + + return services; + } +} + +#nullable disable diff --git a/test/Pipelines.Generator.Test/Resources/IgnoreAttributeTests/SimpleHandler/Snapshot#PipelinesRegistrations.Pipelines.g.verified.cs b/test/Pipelines.Generator.Test/Resources/IgnoreAttributeTests/SimpleHandler/Snapshot#PipelinesRegistrations.Pipelines.g.verified.cs new file mode 100644 index 0000000..b37bd0c --- /dev/null +++ b/test/Pipelines.Generator.Test/Resources/IgnoreAttributeTests/SimpleHandler/Snapshot#PipelinesRegistrations.Pipelines.g.verified.cs @@ -0,0 +1,25 @@ + +#nullable enable + +using global::System.CodeDom.Compiler; +using global::Microsoft.Extensions.DependencyInjection; +using global::Microsoft.Extensions.DependencyInjection.Extensions; +using global::Pipelines; +using global::Pipelines.Requests; +using global::Pipelines.Streams; + +namespace Microsoft.Extensions.DependencyInjection; + +internal static partial class PipelinesRegistrations +{ + public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddPipelines(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services) + { + services.TryAddScoped(typeof(global::Pipelines.Requests.RequestPipeline<,>)); + services.TryAddScoped(typeof(global::Pipelines.Streams.StreamRequestPipeline<,>)); + services.TryAddScoped(); + + return services; + } +} + +#nullable disable diff --git a/test/Pipelines.Generator.Test/_setup.cs b/test/Pipelines.Generator.Test/_setup.cs index 3ddfaed..92159e9 100644 --- a/test/Pipelines.Generator.Test/_setup.cs +++ b/test/Pipelines.Generator.Test/_setup.cs @@ -33,6 +33,7 @@ .. AppDomain.CurrentDomain.GetAssemblies().Where(x => !x.IsDynamic).Select(x => [ "System", "System.Collections.Generic", + "System.Linq", "System.Runtime.CompilerServices", "System.Threading", "System.Threading.Tasks", @@ -61,7 +62,12 @@ .. AppDomain.CurrentDomain.GetAssemblies().Where(x => !x.IsDynamic).Select(x => /// /// Whether to ignore compilation errors or not. /// - public bool IgnoreErrors { get; set; } + public bool IgnoreCompilationErrors { get; set; } + + /// + /// Whether to ignore generator compilation errors or not. + /// + public bool IgnoreGeneratorErrors { get; set; } /// /// References to include in the compilation. @@ -132,6 +138,8 @@ protected async Task Verify(Action? configure = null, [CallerMember options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) ); + AssertNoErrors(compilation.GetDiagnostics(), context.IgnoreCompilationErrors); + var generator = new PipelinesGenerator().AsSourceGenerator(); var driver = (GeneratorDriver)CSharpGeneratorDriver.Create([generator], driverOptions: new(default, true)); driver = driver.RunGenerators(compilation); @@ -139,8 +147,8 @@ protected async Task Verify(Action? configure = null, [CallerMember var runResult1 = driver.GetRunResult(); var runResult2 = driver.RunGenerators(compilation.Clone()).GetRunResult(); - AssertNoErrors(runResult1.Diagnostics, context.IgnoreErrors); - AssertNoErrors(runResult2.Diagnostics, context.IgnoreErrors); + AssertNoErrors(runResult1.Diagnostics, context.IgnoreGeneratorErrors); + AssertNoErrors(runResult2.Diagnostics, context.IgnoreGeneratorErrors); //AssertRunsEqual(runResult1, runResult2, trackingNames); await Verifier