< 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
 42113public class HostMethodActivityDescriber(IActivityDescriber activityDescriber) : IHostMethodActivityDescriber
 14{
 15    public async Task<IEnumerable<ActivityDescriptor>> DescribeAsync(string key, Type hostType, CancellationToken cancel
 16    {
 7217        var methods = hostType
 7218            .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)
 64819            .Where(m => !m.IsSpecialName)
 7220            .ToList();
 21
 7222        var descriptors = new List<ActivityDescriptor>(methods.Count);
 144023        foreach (var method in methods)
 24        {
 64825            var descriptor = await DescribeMethodAsync(key, hostType, method, cancellationToken);
 64826            descriptors.Add(descriptor);
 27        }
 28
 7229        return descriptors;
 7230    }
 31
 32    public async Task<ActivityDescriptor> DescribeMethodAsync(string key, Type hostType, MethodInfo method, Cancellation
 33    {
 64834        var descriptor = await activityDescriber.DescribeActivityAsync(typeof(HostMethodActivity), cancellationToken);
 64835        var activityAttribute = hostType.GetCustomAttribute<ActivityAttribute>() ?? method.GetCustomAttribute<ActivityAt
 36
 64837        var methodName = method.Name;
 64838        var activityTypeName = BuildActivityTypeName(key, method, activityAttribute);
 39
 64840        var displayAttribute = method.GetCustomAttribute<DisplayAttribute>();
 64841        var typeDisplayName = activityAttribute?.DisplayName ?? hostType.GetCustomAttribute<DisplayNameAttribute>()?.Dis
 64842        var methodNameWithoutAsync = StripAsyncSuffix(methodName);
 64843        var methodDisplayName = displayAttribute?.Name ?? methodNameWithoutAsync.Humanize().Transform(To.TitleCase);
 64844        var displayName = !string.IsNullOrWhiteSpace(typeDisplayName) ? typeDisplayName : methodDisplayName;
 64845        if (!string.IsNullOrWhiteSpace(activityAttribute?.DisplayName))
 3646            displayName = activityAttribute.DisplayName!;
 47
 64848        descriptor.Name = methodName;
 64849        descriptor.TypeName = activityTypeName;
 64850        descriptor.DisplayName = displayName;
 64851        descriptor.Description = activityAttribute?.Description ?? method.GetCustomAttribute<DescriptionAttribute>()?.De
 64852        descriptor.Category = activityAttribute?.Category ?? hostType.Name.Humanize().Transform(To.TitleCase);
 64853        descriptor.Kind = activityAttribute?.Kind ?? ActivityKind.Task;
 64854        descriptor.RunAsynchronously = activityAttribute?.RunAsynchronously ?? false;
 64855        descriptor.IsBrowsable = true;
 64856        descriptor.ClrType = typeof(HostMethodActivity);
 57
 64858        descriptor.Constructor = context =>
 64859        {
 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;
 64867        };
 68
 64869        descriptor.Inputs.Clear();
 129670        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
 266479        foreach (var parameter in method.GetParameters())
 80        {
 68481            if (IsSpecialParameter(parameter))
 82                continue;
 83
 84            // If FromServices is used, the parameter is not a workflow input unless explicitly forced via [Input].
 54085            var isFromServices = parameter.GetCustomAttribute<FromServicesAttribute>() != null;
 54086            var isExplicitInput = parameter.GetCustomAttribute<InputAttribute>() != null;
 54087            if (isFromServices && !isExplicitInput)
 88                continue;
 89
 54090            var inputDescriptor = CreateParameterInputDescriptor(parameter);
 54091            descriptor.Inputs.Add(inputDescriptor);
 92        }
 93
 64894        descriptor.Outputs.Clear();
 64895        var outputDescriptor = CreateOutputDescriptor(method);
 64896        if (outputDescriptor != null)
 21697            descriptor.Outputs.Add(outputDescriptor);
 98
 64899        return descriptor;
 648100    }
 101
 102    private string BuildActivityTypeName(string key, MethodInfo method, ActivityAttribute? activityAttribute)
 103    {
 648104        var methodName = StripAsyncSuffix(method.Name);
 105
 648106        if (activityAttribute != null && !string.IsNullOrWhiteSpace(activityAttribute.Namespace))
 107        {
 36108            var typeSegment = activityAttribute.Type ?? methodName;
 36109            return $"{activityAttribute.Namespace}.{typeSegment}";
 110        }
 111
 612112        return $"Elsa.Dynamic.HostMethod.{key.Pascalize()}.{methodName}";
 113    }
 114
 115    private static string StripAsyncSuffix(string name)
 116    {
 1296117        return name.EndsWith("Async", StringComparison.Ordinal)
 1296118            ? name[..^5]
 1296119            : 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    {
 540156        var inputAttribute = parameter.GetCustomAttribute<InputAttribute>();
 540157        var displayNameAttribute = parameter.GetCustomAttribute<DisplayNameAttribute>();
 158
 540159        var inputName = inputAttribute?.Name ?? parameter.Name ?? "input";
 540160        var displayName = inputAttribute?.DisplayName ?? displayNameAttribute?.DisplayName ?? inputName.Humanize();
 540161        var description = inputAttribute?.Description;
 540162        var nakedInputType = parameter.ParameterType;
 163
 540164        return new()
 540165        {
 540166            Name = inputName,
 540167            DisplayName = displayName,
 540168            Description = description,
 540169            Type = nakedInputType,
 0170            ValueGetter = activity => activity.SyntheticProperties.GetValueOrDefault(inputName),
 0171            ValueSetter = (activity, value) => activity.SyntheticProperties[inputName] = value!,
 540172            IsSynthetic = true,
 540173            IsWrapped = true,
 540174            UIHint = inputAttribute?.UIHint ?? ActivityDescriber.GetUIHint(nakedInputType),
 540175            Category = inputAttribute?.Category,
 540176            DefaultValue = inputAttribute?.DefaultValue,
 540177            Order = inputAttribute?.Order ?? 0,
 540178            IsBrowsable = inputAttribute?.IsBrowsable ?? true,
 540179            AutoEvaluate = inputAttribute?.AutoEvaluate ?? true,
 540180            IsSerializable = inputAttribute?.IsSerializable ?? true,
 540181            IsSensitive = inputAttribute?.CanContainSecrets ?? false
 540182        };
 183    }
 184
 185    private OutputDescriptor? CreateOutputDescriptor(MethodInfo method)
 186    {
 648187        var returnType = method.ReturnType;
 188
 189        // No output for void or Task.
 648190        if (returnType == typeof(void) || returnType == typeof(Task))
 432191            return null;
 192
 193        // Determine the "real" return type.
 194        Type actualReturnType;
 216195        if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
 36196            actualReturnType = returnType.GetGenericArguments()[0];
 180197        else if (typeof(Task).IsAssignableFrom(returnType))
 0198            return null;
 199        else
 180200            actualReturnType = returnType;
 201
 216202        var outputAttribute = method.ReturnParameter.GetCustomAttribute<OutputAttribute>() ??
 216203                              method.GetCustomAttribute<OutputAttribute>() ??
 216204                              method.DeclaringType?.GetCustomAttribute<OutputAttribute>();
 205
 216206        var displayNameAttribute = method.ReturnParameter.GetCustomAttribute<DisplayNameAttribute>();
 216207        var outputName = outputAttribute?.Name ?? "Output";
 216208        var displayName = outputAttribute?.DisplayName ?? displayNameAttribute?.DisplayName ?? outputName.Humanize();
 216209        var description = outputAttribute?.Description ?? "The method output.";
 216210        var nakedOutputType = actualReturnType;
 211
 216212        return new()
 216213        {
 216214            Name = outputName,
 216215            DisplayName = displayName,
 216216            Description = description,
 216217            Type = nakedOutputType,
 216218            IsSynthetic = true,
 0219            ValueGetter = activity => activity.SyntheticProperties.GetValueOrDefault(outputName),
 0220            ValueSetter = (activity, value) => activity.SyntheticProperties[outputName] = value!,
 216221            IsBrowsable = outputAttribute?.IsBrowsable ?? true,
 216222            IsSerializable = outputAttribute?.IsSerializable ?? true
 216223        };
 224    }
 225
 226    private static bool IsSpecialParameter(ParameterInfo parameter)
 227    {
 228        // These parameters are supplied by the runtime and should not become input descriptors.
 684229        if (parameter.ParameterType == typeof(CancellationToken))
 36230            return true;
 231
 648232        if (parameter.ParameterType == typeof(ActivityExecutionContext))
 108233            return true;
 234
 540235        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}