< Summary

Information
Class: Elsa.Workflows.Models.JsonActivityConstructorContextHelper
Assembly: Elsa.Workflows.Core
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Core/Models/ActivityConstructorContext.cs
Line coverage
80%
Covered lines: 72
Uncovered lines: 18
Coverable lines: 90
Total lines: 227
Line coverage: 80%
Branch coverage
81%
Covered branches: 52
Total branches: 64
Branch coverage: 81.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Create(...)100%11100%
CreateActivity(...)100%11100%
CreateActivity(...)100%44100%
StripTypeMetadata(...)71.42%1602844.82%
ReadSyntheticInputs(...)83.33%121292.3%
ReadSyntheticOutputs(...)100%1010100%
GetBoolean(...)100%11100%
GetNullableBoolean(...)80%101091.66%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Core/Models/ActivityConstructorContext.cs

#LineLine coverage
 1using System.Text.Json;
 2using System.Text.Json.Nodes;
 3using Elsa.Expressions.Helpers;
 4using Elsa.Extensions;
 5using Elsa.Workflows.Attributes;
 6using Elsa.Workflows.Memory;
 7using Humanizer;
 8
 9namespace Elsa.Workflows.Models;
 10
 11public record ActivityConstructorContext(ActivityDescriptor ActivityDescriptor, Func<Type, IActivity> ActivityFactory)
 12{
 13    public T CreateActivity<T>() where T : IActivity => (T)ActivityFactory(typeof(T));
 14    public IActivity CreateActivity(Type type) => ActivityFactory(type);
 15}
 16
 17public static class JsonActivityConstructorContextHelper
 18{
 19    public static ActivityConstructorContext Create(ActivityDescriptor activityDescriptor, JsonElement element, JsonSeri
 20    {
 716521        return new ActivityConstructorContext(activityDescriptor, type => CreateActivity(activityDescriptor, type, eleme
 22    }
 23
 24    public static T CreateActivity<T>(ActivityDescriptor activityDescriptor, JsonElement element, JsonSerializerOptions 
 25    {
 1226        return (T)CreateActivity(activityDescriptor, typeof(T), element, serializerOptions);
 27    }
 28
 29    public static IActivity CreateActivity(ActivityDescriptor activityDescriptor, Type type, JsonElement element, JsonSe
 30    {
 31        // 1) Grab the raw text
 359232        var raw = element.GetRawText();
 33
 34        // 2) Parse into a JsonNode so we can mutate
 359235        var node = JsonNode.Parse(raw)!;
 36
 37        // 3) Strip out any "_type" metadata wrappers
 359238        StripTypeMetadata(node);
 39
 40        // 4) Get a cleaned JSON string
 359241        var cleanedJson = node.ToJsonString(new(JsonSerializerDefaults.Web));
 42
 43        // 5) Parse it back to a JsonDocument so we can get a clean JsonElement
 359244        using var doc = JsonDocument.Parse(cleanedJson);
 359245        var cleanedElement = doc.RootElement.Clone();
 46
 47        // 6) Deserialize into IActivity, or create a plain instance
 359248        var activity = (IActivity)JsonSerializer.Deserialize(cleanedJson, type, serializerOptions)!;
 49
 50        // 7) Pull out your boolean flags from the cleaned element
 359251        var canStartWorkflow = GetBoolean(cleanedElement, "canStartWorkflow");
 359252        var runAsynchronously = GetNullableBoolean(cleanedElement, "runAsynchronously") ?? activityDescriptor.RunAsynchr
 53
 54        // 8) If composite, setup
 359255        if (activity is IComposite composite)
 55056            composite.Setup();
 57
 58        // 9) Existing synthetic inputs/outputs routines, using the cleanedElement
 359259        ReadSyntheticInputs(activityDescriptor, activity, cleanedElement, serializerOptions);
 358860        ReadSyntheticOutputs(activityDescriptor, activity, cleanedElement);
 61
 62        // 10) Finally re‑apply those flags
 358863        activity.SetCanStartWorkflow(canStartWorkflow);
 358864        activity.SetRunAsynchronously(runAsynchronously);
 65
 358866        return activity;
 358867    }
 68
 69    /// <summary>
 70    /// Recursively remove any "_type" properties and unwrap any
 71    /// { "_type": "...", "items": [ ... ] } or "values": [ ... ] wrappers
 72    /// </summary>
 73    private static void StripTypeMetadata(JsonNode node)
 74    {
 75        switch (node)
 76        {
 77            case JsonObject obj:
 78                // First recurse into each child
 50228979                foreach (var key in obj.Select(kvp => kvp.Key).ToList())
 80                {
 13702781                    var child = obj[key];
 13702782                    if (child is JsonObject wrapper && wrapper.ContainsKey("_type"))
 83                    {
 84                        // If it has an "items" array, replace the whole property with that array
 085                        if (wrapper["items"] is JsonArray items)
 86                        {
 087                            obj[key] = items;
 088                            StripTypeMetadata(items);
 089                            continue;
 90                        }
 91
 92                        // Or a "values" array
 093                        if (wrapper["values"] is JsonArray values)
 94                        {
 095                            obj[key] = values;
 096                            StripTypeMetadata(values);
 097                            continue;
 98                        }
 99                    }
 100
 101                    // Otherwise just recurse normally
 137027102                    if (child != null)
 128181103                        StripTypeMetadata(child);
 104                }
 105
 106                // And remove any stray _type on this object
 45604107                obj.Remove("_type");
 45604108                break;
 109
 110            case JsonArray arr:
 14536111                for (var i = 0; i < arr.Count; i++)
 112                {
 2920113                    var element = arr[i];
 2920114                    if (element is JsonObject w && w.ContainsKey("_type"))
 115                    {
 0116                        if (w["items"] is JsonArray items)
 117                        {
 0118                            arr[i] = items;
 0119                            StripTypeMetadata(items);
 0120                            continue;
 121                        }
 122
 0123                        if (w["values"] is JsonArray values)
 124                        {
 0125                            arr[i] = values;
 0126                            StripTypeMetadata(values);
 0127                            continue;
 128                        }
 129                    }
 130
 2920131                    if (element != null)
 2920132                        StripTypeMetadata(element);
 133                }
 134
 135                break;
 136
 137            // primitives—nothing to do
 138            default:
 139                break;
 140        }
 4348141    }
 142
 143    private static void ReadSyntheticInputs(ActivityDescriptor activityDescriptor, IActivity activity, JsonElement activ
 144    {
 13761145        foreach (var inputDescriptor in activityDescriptor.Inputs.Where(x => x.IsSynthetic))
 146        {
 37147            var inputName = inputDescriptor.Name;
 37148            var propertyName = inputName.Camelize();
 37149            var nakedType = inputDescriptor.Type;
 37150            var wrappedType = typeof(Input<>).MakeGenericType(nakedType);
 151
 37152            if (!activityRoot.TryGetProperty(propertyName, out var propertyElement) || propertyElement.ValueKind == Json
 153                continue;
 154
 37155            var isWrapped = propertyElement.ValueKind == JsonValueKind.Object && propertyElement.GetProperty("typeName")
 156
 33157            if (isWrapped)
 158            {
 33159                var json = propertyElement.ToString();
 33160                var inputValue = JsonSerializer.Deserialize(json, wrappedType, options);
 161
 33162                activity.SyntheticProperties[inputName] = inputValue!;
 163            }
 164            else
 165            {
 0166                activity.SyntheticProperties[inputName] = propertyElement.ConvertTo(inputDescriptor.Type)!;
 167            }
 168        }
 3588169    }
 170
 171    private static void ReadSyntheticOutputs(ActivityDescriptor activityDescriptor, IActivity activity, JsonElement acti
 172    {
 9956173        foreach (var outputDescriptor in activityDescriptor.Outputs.Where(x => x.IsSynthetic))
 174        {
 103175            var outputName = outputDescriptor.Name;
 103176            var propertyName = outputName.Camelize();
 103177            var nakedType = outputDescriptor.Type;
 103178            var wrappedType = typeof(Output<>).MakeGenericType(nakedType);
 179
 103180            if (!activityRoot.TryGetProperty(propertyName, out var propertyElement) || propertyElement.ValueKind == Json
 181                continue;
 182
 33183            var memoryReferenceElement = propertyElement.GetProperty("memoryReference");
 184
 33185            if (!memoryReferenceElement.TryGetProperty("id", out var memoryReferenceIdElement))
 186                continue;
 187
 33188            var variable = new Variable
 33189            {
 33190                Id = memoryReferenceIdElement.GetString()!
 33191            };
 33192            variable.Name = variable.Id;
 193
 33194            var output = Activator.CreateInstance(wrappedType, variable)!;
 195
 33196            activity.SyntheticProperties[outputName] = output!;
 197        }
 3588198    }
 199
 200    private static bool GetBoolean(JsonElement element, string propertyName)
 201    {
 3592202        return GetNullableBoolean(element, propertyName) ?? false;
 203    }
 204
 205    private static bool? GetNullableBoolean(JsonElement element, string propertyName)
 206    {
 7184207        var propertyNames = new[]
 7184208        {
 7184209            propertyName.Camelize(), propertyName.Pascalize()
 7184210        };
 211
 23238212        foreach (var name in propertyNames)
 213        {
 7746214            if (element.TryGetProperty("customProperties", out var customPropertyElement))
 215            {
 6882216                if (customPropertyElement.TryGetProperty(name, out var canStartWorkflowElement))
 6622217                    return (bool?)canStartWorkflowElement.GetValue();
 218            }
 219
 1124220            if (element.TryGetProperty(propertyName.Camelize(), out var property)
 1124221                && (bool?)property.GetValue() is { } propValue)
 0222                return propValue;
 223        }
 224
 562225        return null;
 226    }
 227}