Skip to content

Latest commit

 

History

History
999 lines (719 loc) · 25.3 KB

File metadata and controls

999 lines (719 loc) · 25.3 KB

Troubleshooting


In this section, you'll find descriptions of exceptions that may arise while using 'Pipelines' and guidance on how to debug the source generated dispatcher.


Table of Content


1. Pipeline Builder Exceptions

ProvidedTypeIsNotInterfaceException

What happened?

One of the types provided to the pipeline is not an interface.

Bad example

public class SampleClass{}

services
    .AddPipeline()
      .AddInput(typeof(SampleClass))
      .AddHandler(typeof(IHandler<>), handlersAssembly)
      .AddDispatcher<IDispatcher>(dispatcherAssembly)
    .Build();
...

How to fix

  1. Ensure all provided types are interfaces.
  2. Always use typeof when providing types.
public interface IInput{}

services
    .AddPipeline()
      .AddInput(typeof(IInput))
      .AddHandler(typeof(IHandler<>), handlersAssembly)
      .AddDispatcher<IDispatcher>(dispatcherAssembly)
    .Build();
...

HandlerMethodNotFoundException

What happened?

A provided type doesn't define a handle method.

Bad example

public interface IHandler<in TInput, TResult> where TInput : IInput<TResult> where TResult: class
{}

...

How to fix

Define a Handle method in the handler.

public interface IHandler<in TInput, TResult> where TInput : IInput<TResult> where TResult: class
{
    public Task<TResult> HandleAsync(TInput input, CancellationToken token);
}

...


MultipleHandlerMethodsException

What happened?

Multiple methods were defined in the provided type. Each interface must contain only a single method.

Bad example

public interface IHandler<in TInput, TResult> where TInput : IInput<TResult> where TResult: class
{
    public Task<TResult> HandleAsync(TInput input, CancellationToken token);
    public Task<TResult> HandleAsync(TInput input);
}

How to fix

Remove extra methods from the interface.

public interface IHandler<in TInput, TResult> where TInput : IInput<TResult> where TResult: class
{
    public Task<TResult> HandleAsync(TInput input, CancellationToken token);
}

MethodShouldHaveAtLeastOneParameterException

What happened?

The defined Handle method does not have any parameters.

Bad example

public interface IHandler<in TInput, TResult> where TInput : IInput<TResult> where TResult: class
{
    public Task<TResult> HandleAsync();
}

How to fix

Ensure the method has at least one parameter, which should be of the Input Type.

public interface IHandler<in TInput, TResult> where TInput : IInput<TResult> where TResult: class
{
    public Task<TResult> HandleAsync(TInput input, CancellationToken token);
}

GenericArgumentsNotFoundException

What happened?

The Handler lacks generic arguments. At least one generic argument is required, specifically for Input.

Bad example

public interface IHandler
{
    Task HandleAsync(IInput input, CancellationToken token);
}

How to fix

Ensure the Handler has at least one generic argument representing the Input.

public interface IHandler<in TInput> where TInput : IInput
{
    Task HandleAsync(TInput input, CancellationToken token);
}

HandlerInputTypeMismatchException

What happened?

The first generic argument doesn't have the Input type as its constraint.

Bad example

public interface IInput
{ }

public interface IHandlerWithResult<in TInput, TResult> 
    where TInput : IInputWithResult<TResult> 
    where TResult : class
{
    Task<TResult> HandleAsync(TInput input, CancellationToken token);
}

How to fix

The first generic argument of the handler must use the Input type, specified in the AddInput() method, as its constraint.

public interface IInput
{ }

public interface IHandler<in TInput> where TInput : IInput
{
    Task HandleAsync(TInput input, CancellationToken token);
}

InvalidConstraintLengthException

What happened?

The first generic argument lacks constraints. At least one constraint is required, specifically for the Input type.

Bad example

public interface IInput
{ }

public interface IHandler<in TInput>
{
    public Task HandleAsync(TInput input, CancellationToken token);
}

How to fix

Ensure the first generic argument of the handler has the Input type as its constraint.

public interface IInput
{ }

public interface IHandler<in TInput> where TInput : IInput
{
    Task HandleAsync(TInput input, CancellationToken token);
}

ExpectedMethodWithResultException

What happened?

Given the Input generic arguments, it was anticipated that the Handler/Dispatcher would have a method returning a result, but a void was found instead.

Bad example

public interface IInput<TResult>
{}

public interface IDispatcher
{
    public Task SendAsync<TResult>(IInput<TResult> request, CancellationToken token);
}

public interface IHandler<in TInput, TResult> where TInput : IInput<TResult>
{
    public Task HandleAsync(TInput command, CancellationToken token);
}

How to fix

Ensure that result types align with the Input generic arguments.

public interface IInput<TResult>
{}

public interface IDispatcher
{
    public Task<TResult> SendAsync<TResult>(IInput<TResult> request, CancellationToken token);
}

public interface IHandler<in TInput, TResult> where TInput : IInput<TResult>
{
    public Task<TResult> HandleAsync(TInput command, CancellationToken token);
}

ResultTypeCountMismatchException

What happened?

The number of result types defined by the Input generic arguments does not match the number of result types found in the Dispatcher/Handler method.

Bad example

public interface IInputWithResult<TResult>
{ }


public interface IHandler<in TInput, TResult>
    where TInput : IInputWithResult<TResult> where TResult : class
{
    public Task<(TResult, bool)> HandleAsync(TInput command, CancellationToken token);
}

public interface IDispatcher
{
    public Task<(TResult, TResult2)> SendAsync<TResult,TResult2>(IInputWithResult<TResult> request, CancellationToken token);
}

How to fix

Ensure that the Dispatcher/Handler method's result types align with the Input generic arguments.

public interface IInputWithResult<TResult>
{ }


public interface IHandler<in TInput, TResult>
    where TInput : IInputWithResult<TResult> where TResult : class
{
    public Task<TResult> HandleAsync(TInput command, CancellationToken token);
}

public interface IDispatcher
{
    public Task<TResult> SendAsync<TResult>(IInputWithResult<TResult> request, CancellationToken token);
}

GenericTypeCountMismatchException

What happened?

The number of constraints in the result types defined in the Handler or Dispatcher does not match.

Bad example

public interface IInputType
{}

public interface IHandler<in TInput, TResultOne, TResultTwo> where TInput : IInputType
    where TResultOne : IResultOne
    where TResultTwo : IResultTwo
{
    public Task<(TResultOne,TResultTwo)> HandleAsync(TInput command, CancellationToken token);
}

public interface IDispatcher
{
    public Task<(TResult, TResultTwo)> SendAsync<TResult, TResultTwo>(IInputType inputType)
        where TResult : IResultOne;
}

How to fix

Ensure that the number of constraints on result types in the Handler or Dispatcher matches.

public interface IInputType
{}

public interface IHandler<in TInput, TResultOne, TResultTwo> where TInput : IInputType
    where TResultOne : IResultOne
    where TResultTwo : IResultTwo
{
    public Task<(TResultOne,TResultTwo)> HandleAsync(TInput command, CancellationToken token);
}

public interface IDispatcher
{
    public Task<(TResult, TResultTwo)> SendAsync<TResult, TResultTwo>(IInputType inputType)
        where TResult : IResultOne
        where TResultTwo : IResultTwo;
}

GenericTypeMismatchException

What happened?

There is a mismatch in the generic type constraints between the Handler and the Dispatcher, or between the Input result types constraint and the Handler or Dispatcher.

Bad example

public interface IInputType
{ }

public interface IHandler<in TInput, TResultOne, TResultTwo> where TInput : IInputType
    where TResultOne : IResultOne
    where TResultTwo : IResultTwo
{
    public Task<(TResultOne,TResultTwo)> HandleAsync(TInput command, CancellationToken token);
}

public interface IDispatcher
{
    public Task<(TResult, TResultTwo)> SendAsync<TResult, TResultTwo>(IInputType inputType, CancellationToken token)
        where TResult : IResultOne where TResultTwo : struct;
}

How to fix

Ensure that both the Handler and Dispatcher are consistent in their return types, and their generic type constraints match accordingly.

public interface IHandler<in TInput, TResultOne, TResultTwo> where TInput : IInputType
    where TResultOne : IResultOne
    where TResultTwo : IResultTwo
{
    public Task<(TResultOne,TResultTwo)> HandleAsync(TInput command, CancellationToken token);
}

public interface IDispatcher
{
    public Task<(TResult, TResultTwo)> SendAsync<TResult, TResultTwo>(IInputType inputType, CancellationToken token)
        where TResult : IResultOne where TResultTwo : IResultTwo;
}

IsGenericMismatchException

What happened?

There's a mismatch between the result types in the Handler and Dispatcher: one is generic, while the other is not.

Bad example

public interface IHandler<in TInput, TResult> where TInput : IInputType
    where TResult : IResult
{
    public Task<TResult> HandleAsync(TInput input, CancellationToken token);
}

public interface IDispatcher
{
    public Task<string> SendAsync(IInputType inputType, CancellationToken token);
}

How to fix

Ensure that both the Handler and Dispatcher return the same type of result.

public interface IHandler<in TInput, TResult> where TInput : IInputType
    where TResult : IResult
{
    public Task<TResult> HandleAsync(TInput input, CancellationToken token);
}

public interface IDispatcher
{
    public Task<TResult> SendAsync<TResult>(IInputType inputType, CancellationToken token);
}

TypeMismatchException

There's a mismatch between the non generic result types in the Handler and Dispatcher.

What happened?

Bad example

public interface IHandler<in TInput> where TInput : IInputType
{
    public int HandleAsync(TInput input, CancellationToken token);
}

public interface IDispatcher
{
    public string SendAsync(IInputType input);
}

How to fix

Ensure that both the Handler and Dispatcher return the same type of result.

public interface IHandler<in TInput> where TInput : IInputType
{
    public int HandleAsync(TInput input, CancellationToken token);
}

public interface IDispatcher
{
    public int SendAsync(IInputType input);
}

DispatcherMethodInputTypeMismatchException

What happened?

The Dispatcher's Handle method does not use the expected Input type as its first parameter.

Bad example

public interface IInput
{ }

public interface IDispatcher
{
    public Task SendAsync(int request, CancellationToken token);
}

How to fix

Ensure that the Input type is used as the first parameter in the method.

public interface IInput
{ }

public interface IDispatcher
{
    public Task SendAsync(IInput request, CancellationToken token);
}

ParameterCountMismatchException

What happened?

The Handler and Dispatcher methods have a mismatch in the number of parameters.

Bad example

public interface IHandler<in TInput> where TInput : IInputType
{
    public Task<string> HandleAsync(TInput input, CancellationToken token);
}

public interface IDispatcher
{
    public Task<string> SendAsync(IInputType inputType, int index, CancellationToken token);
}

How to fix

Ensure that the Handler and Dispatcher methods have the same number and type of parameters.

public interface IHandler<in TInput> where TInput : IInputType
{
    public Task<string> HandleAsync(TInput input, int index, CancellationToken token);
}

public interface IDispatcher
{
    public Task<string> SendAsync(IInputType inputType, int index, CancellationToken token);
}

ParameterTypeMismatchException

What happened?

There's a mismatch in the type of a parameter between the Handler and Dispatcher methods.

Bad example

public interface IHandler<in TInput> where TInput : IInputType
{
    public Task<string> HandleAsync(TInput command, int index, CancellationToken token);
}

public interface IDispatcher
{
    public Task<string> SendAsync(IInputType inputType, string text, CancellationToken token);
}

How to fix

Ensure that the parameters in both the Handler and Dispatcher methods are of the same type and order.

public interface IHandler<in TInput> where TInput : IInputType
{
    public Task<string> HandleAsync(TInput command, string text, CancellationToken token);
}

public interface IDispatcher
{
    public Task<string> SendAsync(IInputType inputType, string text, CancellationToken token);
}

TaskReturnTypeMismatchException

What happened?

There's a mismatch in the return type of the Handler and Dispatcher methods. One method returns a Task<> type, while the other does not.

Bad example

public interface IHandler<in TInput> where TInput : IInputType
{
    public string HandleAsync(TInput command, CancellationToken token);
}

public interface IDispatcher
{
    public Task<string> SendAsync(IInputType inputType, CancellationToken token);
}

How to fix

Ensure that both the Handler and Dispatcher methods have consistent return types. If one returns a Task<>, the other should too.

public interface IHandler<in TInput> where TInput : IInputType
{
    public Task<string> HandleAsync(TInput command, CancellationToken token);
}

public interface IDispatcher
{
    public Task<string> SendAsync(IInputType inputType, CancellationToken token);
}

VoidAndValueMethodMismatchException

What happened?

There's an inconsistency in the return types between the Handler and Dispatcher methods. One method returns a value, while the other returns void.

Bad example

public interface IHandler<in TInput> where TInput : IInputType
{
    public string HandleAsync(TInput input, CancellationToken token);
}

public interface IDispatcherVoid
{
    public void SendAsync(IInputType inputType);
}

How to fix

Ensure both the Handler and Dispatcher methods have matching return types. Either both should return a value, or both should return void.

public interface IHandler<in TInput> where TInput : IInputType
{
    public Task<string> HandleAsync(TInput input, CancellationToken token);
}

public interface IDispatcher
{
    public Task<string> SendAsync(IInputType inputType);
}

InterfaceImplementationException

What happened?

The decorator class is not implementing the expected interface, leading to a type mismatch between the expected and actual generic types.

Bad example

public interface IHandler<in TInput> where TInput : IInput
{
    public Task HandleAsync(TInput input, CancellationToken token);
}

public class Decorator : IDifferentHandler<InputWithResult, Result>
{
    private readonly IDifferentHandler<InputWithResult, Result> _handler;

    public Decorator(IDifferentHandler<InputWithResult, Result> handler)
    {
        _handler = handler;
    }

    public async Task<Result> HandleAsync(InputWithResult input, CancellationToken token)
    {
        return await _handler.HandleAsync(input, token);
    }
}

How to fix

Ensure that the decorator class implements the correct interface, which it's intended to decorate. The generic type parameters of the decorator should align with those of the interface it should implement.

public class Decorator : IHandler<IInput, Result>
{
    private readonly IHandler<IInput, Result> _handler;

    public Decorator(IHandler<IInput, Result> handler)
    {
        _handler = handler;
    }

    public async Task<Result> HandleAsync(IInput input, CancellationToken token)
    {
        return await _handler.HandleAsync(input, token);
    }
}

ConstructorValidationException

What happened?

The decorator's constructor does not have the required handler dependency (either it's missing or invalid).

Bad example

public interface IHandler<in TInput> where TInput : IInput
{
    public Task HandleAsync(TInput input, CancellationToken token);
}

public class Decorator : IHandler<IInput, Result>
{
    public Decorator()
    { }

    public async Task<Result> HandleAsync(IInput input, CancellationToken token)
    {
        return Task.CompletedTask;
    }
}

How to fix

Include the required handler dependency in the decorator's constructor and use it in the HandleAsync method:

public class Decorator : IHandler<IInput, Result>
{
    private readonly IHandler<IInput, Result> _handler;

    public Decorator(IHandler<IInput, Result> handler)
    {
        _handler = handler;
    }

    public async Task<Result> HandleAsync(IInput input, CancellationToken token)
    {
        return await _handler.HandleAsync(input, token);
    }
}

2. Runtime Exceptions

DispatcherNotRegisteredException

What happened?

The dispatcher registration is missing from the Dependency Injection Container. By default, the AddDispatcher() method use the Dispatcher Generated by Source generator. The primary reason the Generated Dispatcher isn't found is due to the absence of package in project Pipelines.WrapperDispatcherGenerator.

How to fix

Add package DumplingsDevs.Pipelines.WrapperDispatcherGenerator or change Dispatcher options to UseReflectionProxyImplementation=true

dotnet add package DumplingsDevs.Pipelines.WrapperDispatcherGenerator

or

services
    .AddPipeline()
    .AddInput(typeof(ICommand<>))
    .AddHandler(typeof(ICommandHandler<,>), assembly)
    .AddDispatcher<ICommandDispatcher>(new DispatcherOptions(true)
    .Build()

Additional Possibility:

What happened?

Another possible issue could be related to problems during the generation of the dispatcher. In this situation, we recommend creating an issue on our GitHub repository: https://github.com/DumplingsDevs/Pipelines/issues. Please provide a detailed description of your Pipelines configuration to help us assist you better.


HandlerNotRegisteredException

What happened?

The handler implementation for the provided input was not found. In most cases, this suggests that while the input has been defined, its corresponding handler implementation is missing.

...

Bad example

public record ExampleCommand2(string Value) : IInput<ExampleCommandResult>;
...

var request = new ExampleCommand2("My test request");
var result = await _dispatcher.SendAsync(request, new CancellationToken());

How to fix

For every defined Input, ensure that a corresponding Handler is implemented.

public record ExampleCommand2(string Value) : IInput<ExampleCommandResult>;

public class ExampleHandler : IHandler<ExampleCommand2, ExampleCommandResult>
{
    public Task<ExampleCommandResult> HandleAsync(ExampleCommand2 input, CancellationToken token)
    {
        return Task.FromResult(new ExampleCommandResult(input.Value));
    }
}
...

var request = new ExampleCommand2("My test request");
var result = await _dispatcher.SendAsync(request, new CancellationToken());

InputNullReferenceException

What happened?

The dispatcher was invoked with a null value instead of an actual input object.

Bad example

var result = await _dispatcher.SendAsync(null, new CancellationToken());

How to fix

Ensure that you provide a valid input object to the method and avoid passing null values.

var request = new ExampleCommand2("My test request");
var result = await _dispatcher.SendAsync(request, new CancellationToken());

CannotCreateDispatcherWrapperException

What happened?

Something went wrong with source generator and generated Dispatcher. Please create issue on Github with types and builder description.


AssemblyNotProvidedException

What happened?

At least one assembly is not provided to AddHandler() method or WithDecorators

Bad example

services
    .AddPipeline()
    .AddInput(typeof(ICommand<>))
    .AddHandler(typeof(ICommandHandler<,>))
    .AddDispatcher<ICommandDispatcher>(new DispatcherOptions(true,dispatcherAssembly))
    .Build()
services
    .AddPipeline()
    .AddInput(typeof(ICommand<>))
    .AddHandler(typeof(ICommandHandler<,>), assembly)
    .AddDispatcher<ICommandDispatcher>(new DispatcherOptions(true, dispatcherAssembly))
    .WithDecorators(x => x.WithNameContaining("Attribute"))
    .Build()

How to fix

Ensure that you provided at least one assembly to AddHandler() or WithDecorators.

services
    .AddPipeline()
    .AddInput(typeof(ICommand<>))
    .AddHandler(typeof(ICommandHandler<,>), handlersAssembly)
    .AddDispatcher<ICommandDispatcher>(new DispatcherOptions(true,dispatcherAssembly))
    .Build()
services
    .AddPipeline()
    .AddInput(typeof(ICommand<>))
    .AddHandler(typeof(ICommandHandler<,>), handlersAssembly)
    .AddDispatcher<ICommandDispatcher>(new DispatcherOptions(true, dispatcherAssembly))
    .WithDecorators(x => x.WithNameContaining("Attribute")), decoratorsAssembly)
    .Build()

3. Cannot build project - how to debug

What happened?

In some cases, the Pipelines source generator may generate code that cannot be compiled. This can happen when users create pipelines builders with objects that are not supported by the Pipelines library. If you suspect that this is the issue you're facing, there are specific symptoms to look out for and steps to diagnose and resolve the problem.

Symptoms of Generated Dispatcher Issues:

  1. Build Result Contains Errors in "Pipelines.WrapperDispatcherGenerator" Files:
  • One indication of a problem with the generated dispatcher is the presence of build errors that are logged in files with "Pipelines.WrapperDispatcherGenerator" in the file path. These errors typically relate to issues within the generated code.
  1. Build Errors Disappear After Removing the "Pipelines.WrapperDispatcherGenerator" NuGet Package:
  • Another significant clue is that when you remove the "Pipelines.WrapperDispatcherGenerator" NuGet package from your project, the build errors related to it no longer appear. This suggests that the issue is indeed connected to the generated dispatcher.

Additionally, if the problem you're facing is a blocker for your project, a workaround is to remove the "Pipelines.WrapperDispatcherGenerator" package and utilize the built-in ProxyImplementation that uses reflection to handle dispatcher functionality. To enable the proxy, set the option UseReflectionProxyImplementation to true. Here's a code example:

services
    .AddPipeline()
    .AddInput(typeof(ICommand<>))
    .AddHandler(typeof(ICommandHandler<,>), assembly)
    .AddDispatcher<ICommandDispatcher>(new DispatcherOptions(true)
    .Build()

We encourage you to report any bug as a GitHub issue on our repository at https://github.com/DumplingsDevs/Pipelines/issues. Your feedback helps us improve the library and assists other users who may encounter similar problems.