< Summary

Information
Class: Elsa.Workflows.Management.Services.HostMethodActivityDescriber
Assembly: Elsa.Workflows.Management
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Management/Services/HostMethodActivityDescriber.cs
Line coverage
70%
Covered lines: 110
Uncovered lines: 46
Coverable lines: 156
Total lines: 248
Line coverage: 70.5%
Branch coverage
58%
Covered branches: 104
Total branches: 178
Branch coverage: 58.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
DescribeAsync()100%22100%
DescribeMethodAsync()86%515092.68%
BuildActivityTypeName(...)83.33%66100%
StripAsyncSuffix(...)50%22100%
CreatePropertyInputDescriptor(...)0%1332360%
CreateParameterInputDescriptor(...)61.76%353492%
CreateOutputDescriptor(...)77.77%383689.28%
IsSpecialParameter(...)100%44100%
IsInputProperty(...)0%4260%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Management/Services/HostMethodActivityDescriber.cs

#LineLine coverage
 1using System.ComponentModel;
 2using System.ComponentModel.DataAnnotations;
 3using System.Reflection;
 4using Elsa.Extensions;
 5using Elsa.Workflows.Attributes;
 6using Elsa.Workflows.Management.Activities.HostMethod;
 7using Elsa.Workflows.Management.Attributes;
 8using Elsa.Workflows.Models;
 9using Humanizer;
 10
 11namespace Elsa.Workflows.Management.Services;
 12
 42613public class HostMethodActivityDescriber(IActivityDescriber activityDescriber) : IHostMethodActivityDescriber
 14{
 15    public async Task<IEnumerable<ActivityDescriptor>> DescribeAsync(string key, Type hostType, CancellationToken cancel
 16    {
 617        var methods = hostType
 618            .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)
 5419            .Where(m => !m.IsSpecialName)
 620            .ToList();
 21
 622        var descriptors = new List<ActivityDescriptor>(methods.Count);
 12023        foreach (var method in methods)
 24        {
 5425            var descriptor = await DescribeMethodAsync(key, hostType, method, cancellationToken);
 5426            descriptors.Add(descriptor);
 27        }
 28
 629        return descriptors;
 630    }
 31
 32    public async Task<ActivityDescriptor> DescribeMethodAsync(string key, Type hostType, MethodInfo method, Cancellation
 33    {
 5434        var descriptor = await activityDescriber.DescribeActivityAsync(typeof(HostMethodActivity), cancellationToken);
 5435        var activityAttribute = hostType.GetCustomAttribute<ActivityAttribute>() ?? method.GetCustomAttribute<ActivityAt
 36
 5437        var methodName = method.Name;
 5438        var activityTypeName = BuildActivityTypeName(key, method, activityAttribute);
 39
 5440        var displayAttribute = method.GetCustomAttribute<DisplayAttribute>();
 5441        var typeDisplayName = activityAttribute?.DisplayName ?? hostType.GetCustomAttribute<DisplayNameAttribute>()?.Dis
 5442        var methodNameWithoutAsync = StripAsyncSuffix(methodName);
 5443        var methodDisplayName = displayAttribute?.Name ?? methodNameWithoutAsync.Humanize().Transform(To.TitleCase);
 5444        var displayName = !string.IsNullOrWhiteSpace(typeDisplayName) ? typeDisplayName : methodDisplayName;
 5445        if (!string.IsNullOrWhiteSpace(activityAttribute?.DisplayName))
 346            displayName = activityAttribute.DisplayName!;
 47
 5448        descriptor.Name = methodName;
 5449        descriptor.TypeName = activityTypeName;
 5450        descriptor.DisplayName = displayName;
 5451        descriptor.Description = activityAttribute?.Description ?? method.GetCustomAttribute<DescriptionAttribute>()?.De
 5452        descriptor.Category = activityAttribute?.Category ?? hostType.Name.Humanize().Transform(To.TitleCase);
 5453        descriptor.Kind = activityAttribute?.Kind ?? ActivityKind.Task;
 5454        descriptor.RunAsynchronously = activityAttribute?.RunAsynchronously ?? false;
 5455        descriptor.IsBrowsable = true;
 5456        descriptor.ClrType = typeof(HostMethodActivity);
 57
 5458        descriptor.Constructor = context =>
 5459        {
 060            var activityResult = context.CreateActivity<HostMethodActivity>();
 061            var activity = activityResult.Activity;
 062            activity.Type = activityTypeName;
 063            activity.HostType = hostType;
 064            activity.MethodName = methodName;
 065            activity.RunAsynchronously ??= descriptor.RunAsynchronously;
 066            return activityResult;
 5467        };
 68
 5469        descriptor.Inputs.Clear();
 10870        foreach (var prop in hostType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
 71        {
 072            if (!IsInputProperty(prop))
 73                continue;
 74
 075            var inputDescriptor = CreatePropertyInputDescriptor(prop);
 076            descriptor.Inputs.Add(inputDescriptor);
 77        }
 78
 22279        foreach (var parameter in method.GetParameters())
 80        {
 5781            if (IsSpecialParameter(parameter))
 82                continue;
 83
 84            // If FromServices is used, the parameter is not a workflow input unless explicitly forced via [Input].
 4585            var isFromServices = parameter.GetCustomAttribute<FromServicesAttribute>() != null;
 4586            var isExplicitInput = parameter.GetCustomAttribute<InputAttribute>() != null;
 4587            if (isFromServices && !isExplicitInput)
 88                continue;
 89
 4590            var inputDescriptor = CreateParameterInputDescriptor(parameter);
 4591            descriptor.Inputs.Add(inputDescriptor);
 92        }
 93
 5494        descriptor.Outputs.Clear();
 5495        var outputDescriptor = CreateOutputDescriptor(method);
 5496        if (outputDescriptor != null)
 1897            descriptor.Outputs.Add(outputDescriptor);
 98
 5499        return descriptor;
 54100    }
 101
 102    private string BuildActivityTypeName(string key, MethodInfo method, ActivityAttribute? activityAttribute)
 103    {
 54104        var methodName = StripAsyncSuffix(method.Name);
 105
 54106        if (activityAttribute != null && !string.IsNullOrWhiteSpace(activityAttribute.Namespace))
 107        {
 3108            var typeSegment = activityAttribute.Type ?? methodName;
 3109            return $"{activityAttribute.Namespace}.{typeSegment}";
 110        }
 111
 51112        return $"Elsa.Dynamic.HostMethod.{key.Pascalize()}.{methodName}";
 113    }
 114
 115    private static string StripAsyncSuffix(string name)
 116    {
 108117        return name.EndsWith("Async", StringComparison.Ordinal)
 108118            ? name[..^5]
 108119            : name;
 120    }
 121
 122    private InputDescriptor CreatePropertyInputDescriptor(PropertyInfo prop)
 123    {
 0124        var inputAttribute = prop.GetCustomAttribute<InputAttribute>();
 0125        var displayNameAttribute = prop.GetCustomAttribute<DisplayNameAttribute>();
 0126        var descriptionAttribute = prop.GetCustomAttribute<DescriptionAttribute>();
 127
 0128        var inputName = inputAttribute?.Name ?? prop.Name;
 0129        var displayName = inputAttribute?.DisplayName ?? displayNameAttribute?.DisplayName ?? prop.Name.Humanize();
 0130        var description = inputAttribute?.Description ?? descriptionAttribute?.Description;
 0131        var nakedInputType = prop.PropertyType;
 132
 0133        return new()
 0134        {
 0135            Name = inputName,
 0136            DisplayName = displayName,
 0137            Description = description,
 0138            Type = nakedInputType,
 0139            ValueGetter = activity => activity.SyntheticProperties.GetValueOrDefault(inputName),
 0140            ValueSetter = (activity, value) => activity.SyntheticProperties[inputName] = value!,
 0141            IsSynthetic = true,
 0142            IsWrapped = true,
 0143            UIHint = inputAttribute?.UIHint ?? ActivityDescriber.GetUIHint(nakedInputType),
 0144            Category = inputAttribute?.Category,
 0145            DefaultValue = inputAttribute?.DefaultValue,
 0146            Order = inputAttribute?.Order ?? 0,
 0147            IsBrowsable = inputAttribute?.IsBrowsable ?? true,
 0148            AutoEvaluate = inputAttribute?.AutoEvaluate ?? true,
 0149            IsSerializable = inputAttribute?.IsSerializable ?? true,
 0150            IsSensitive = inputAttribute?.CanContainSecrets ?? false
 0151        };
 152    }
 153
 154    private InputDescriptor CreateParameterInputDescriptor(ParameterInfo parameter)
 155    {
 45156        var inputAttribute = parameter.GetCustomAttribute<InputAttribute>();
 45157        var displayNameAttribute = parameter.GetCustomAttribute<DisplayNameAttribute>();
 158
 45159        var inputName = inputAttribute?.Name ?? parameter.Name ?? "input";
 45160        var displayName = inputAttribute?.DisplayName ?? displayNameAttribute?.DisplayName ?? inputName.Humanize();
 45161        var description = inputAttribute?.Description;
 45162        var nakedInputType = parameter.ParameterType;
 163
 45164        return new()
 45165        {
 45166            Name = inputName,
 45167            DisplayName = displayName,
 45168            Description = description,
 45169            Type = nakedInputType,
 0170            ValueGetter = activity => activity.SyntheticProperties.GetValueOrDefault(inputName),
 0171            ValueSetter = (activity, value) => activity.SyntheticProperties[inputName] = value!,
 45172            IsSynthetic = true,
 45173            IsWrapped = true,
 45174            UIHint = inputAttribute?.UIHint ?? ActivityDescriber.GetUIHint(nakedInputType),
 45175            Category = inputAttribute?.Category,
 45176            DefaultValue = inputAttribute?.DefaultValue,
 45177            Order = inputAttribute?.Order ?? 0,
 45178            IsBrowsable = inputAttribute?.IsBrowsable ?? true,
 45179            AutoEvaluate = inputAttribute?.AutoEvaluate ?? true,
 45180            IsSerializable = inputAttribute?.IsSerializable ?? true,
 45181            IsSensitive = inputAttribute?.CanContainSecrets ?? false
 45182        };
 183    }
 184
 185    private OutputDescriptor? CreateOutputDescriptor(MethodInfo method)
 186    {
 54187        var returnType = method.ReturnType;
 188
 189        // No output for void or Task.
 54190        if (returnType == typeof(void) || returnType == typeof(Task))
 36191            return null;
 192
 193        // Determine the "real" return type.
 194        Type actualReturnType;
 18195        if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
 3196            actualReturnType = returnType.GetGenericArguments()[0];
 15197        else if (typeof(Task).IsAssignableFrom(returnType))
 0198            return null;
 199        else
 15200            actualReturnType = returnType;
 201
 18202        var outputAttribute = method.ReturnParameter.GetCustomAttribute<OutputAttribute>() ??
 18203                              method.GetCustomAttribute<OutputAttribute>() ??
 18204                              method.DeclaringType?.GetCustomAttribute<OutputAttribute>();
 205
 18206        var displayNameAttribute = method.ReturnParameter.GetCustomAttribute<DisplayNameAttribute>();
 18207        var outputName = outputAttribute?.Name ?? "Output";
 18208        var displayName = outputAttribute?.DisplayName ?? displayNameAttribute?.DisplayName ?? outputName.Humanize();
 18209        var description = outputAttribute?.Description ?? "The method output.";
 18210        var nakedOutputType = actualReturnType;
 211
 18212        return new()
 18213        {
 18214            Name = outputName,
 18215            DisplayName = displayName,
 18216            Description = description,
 18217            Type = nakedOutputType,
 18218            IsSynthetic = true,
 0219            ValueGetter = activity => activity.SyntheticProperties.GetValueOrDefault(outputName),
 0220            ValueSetter = (activity, value) => activity.SyntheticProperties[outputName] = value!,
 18221            IsBrowsable = outputAttribute?.IsBrowsable ?? true,
 18222            IsSerializable = outputAttribute?.IsSerializable ?? true
 18223        };
 224    }
 225
 226    private static bool IsSpecialParameter(ParameterInfo parameter)
 227    {
 228        // These parameters are supplied by the runtime and should not become input descriptors.
 57229        if (parameter.ParameterType == typeof(CancellationToken))
 3230            return true;
 231
 54232        if (parameter.ParameterType == typeof(ActivityExecutionContext))
 9233            return true;
 234
 45235        return false;
 236    }
 237
 238    private static bool IsInputProperty(PropertyInfo prop)
 239    {
 0240        if (!prop.CanRead || !prop.CanWrite)
 0241            return false;
 242
 0243        if (prop.GetIndexParameters().Length > 0)
 0244            return false;
 245
 0246        return true;
 247    }
 248}