< Summary

Information
Class: Elsa.Workflows.Management.Handlers.Notifications.ValidateOutputConverters
Assembly: Elsa.Workflows.Management
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Management/Handlers/Notifications/ValidateOutputConverters.cs
Line coverage
82%
Covered lines: 43
Uncovered lines: 9
Coverable lines: 52
Total lines: 115
Line coverage: 82.6%
Branch coverage
73%
Covered branches: 19
Total branches: 26
Branch coverage: 73%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
HandleAsync()100%1010100%
ValidateBinding(...)66.66%171268%
AddError()100%11100%
CanAssign(...)25%5466.66%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Management/Handlers/Notifications/ValidateOutputConverters.cs

#LineLine coverage
 1using Elsa.Mediator.Contracts;
 2using Elsa.Workflows.Management.Models;
 3using Elsa.Workflows.Management.Notifications;
 4using Elsa.Workflows.Models;
 5using Microsoft.Extensions.DependencyInjection;
 6
 7namespace Elsa.Workflows.Management.Handlers.Notifications;
 8
 9/// <summary>
 10/// Validates output-converter bindings before a workflow definition is accepted.
 11/// </summary>
 40112public class ValidateOutputConverters(
 40113    IWorkflowGraphBuilder workflowGraphBuilder,
 40114    IActivityRegistryLookupService activityRegistryLookupService,
 40115    IOutputConverterRegistry outputConverterRegistry,
 40116    IOutputBindingDestinationResolver destinationResolver,
 40117    IOutputConverterSettingsValidator settingsValidator,
 40118    IServiceProvider serviceProvider) : INotificationHandler<WorkflowDefinitionValidating>
 19{
 20    /// <inheritdoc />
 21    public async Task HandleAsync(WorkflowDefinitionValidating notification, CancellationToken cancellationToken)
 22    {
 823        var graph = await workflowGraphBuilder.BuildAsync(notification.Workflow, cancellationToken);
 24
 5825        foreach (var node in graph.Nodes)
 26        {
 2127            var activity = node.Activity;
 2128            var activityDescriptor = await activityRegistryLookupService.FindAsync(activity.Type, activity.Version);
 29
 2130            if (activityDescriptor == null)
 31                continue;
 32
 8833            foreach (var outputDescriptor in activityDescriptor.Outputs)
 34            {
 2335                if (outputDescriptor.ValueGetter(activity) is not Output { Converter: { } configuration } output)
 36                    continue;
 37
 338                ValidateBinding(
 339                    graph,
 340                    node,
 341                    output,
 342                    outputDescriptor,
 343                    configuration,
 344                    notification.ValidationErrors);
 45            }
 2146        }
 847    }
 48
 49    private void ValidateBinding(
 50        WorkflowGraph graph,
 51        ActivityNode node,
 52        Output output,
 53        OutputDescriptor outputDescriptor,
 54        OutputConverterConfiguration configuration,
 55        ICollection<WorkflowValidationError> validationErrors)
 56    {
 357        var activity = node.Activity;
 58
 59        void AddError(string message) =>
 260            validationErrors.Add(new($"Output '{outputDescriptor.Name}' converter: {message}", activity.Id));
 61
 362        if (string.IsNullOrWhiteSpace(configuration.Id))
 63        {
 064            AddError("a converter ID is required.");
 065            return;
 66        }
 67
 368        var destination = destinationResolver.Resolve(graph, node, output);
 69
 370        if (destination == null)
 71        {
 172            AddError("a converter can only be configured when the output has a variable or workflow-output destination."
 173            return;
 74        }
 75
 276        var registration = outputConverterRegistry.FindRegistration(configuration.Id);
 77
 278        if (registration == null)
 79        {
 180            AddError($"converter '{configuration.Id}' is not registered.");
 181            return;
 82        }
 83
 184        var converterDescriptor = registration.Descriptor;
 85
 186        if (!converterDescriptor.SourceType.IsAssignableFrom(outputDescriptor.Type))
 087            AddError($"converter '{configuration.Id}' cannot accept declared source type '{outputDescriptor.Type.FullNam
 88
 189        if (!CanAssign(converterDescriptor.ResultType, destination.Type))
 090            AddError($"converter '{configuration.Id}' result type '{converterDescriptor.ResultType.FullName}' cannot be 
 91
 92        IOutputConverter converter;
 93
 94        try
 95        {
 196            converter = serviceProvider.GetRequiredKeyedService<IOutputConverter>(registration.ServiceKey);
 197        }
 098        catch
 99        {
 0100            AddError($"converter '{configuration.Id}' could not be resolved.");
 0101            return;
 102        }
 103
 2104        foreach (var settingsError in settingsValidator.Validate(converterDescriptor, converter, configuration.Settings)
 0105            AddError($"converter '{configuration.Id}' settings are invalid: {settingsError}");
 1106    }
 107
 108    private static bool CanAssign(Type sourceType, Type destinationType)
 109    {
 1110        if (destinationType.IsAssignableFrom(sourceType))
 1111            return true;
 112
 0113        return Nullable.GetUnderlyingType(destinationType)?.IsAssignableFrom(sourceType) == true;
 114    }
 115}