< 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
79%
Covered lines: 75
Uncovered lines: 19
Coverable lines: 94
Total lines: 238
Line coverage: 79.7%
Branch coverage
80%
Covered branches: 53
Total branches: 66
Branch coverage: 80.3%
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(...)83.33%6695.23%
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    {
 519021        return new ActivityConstructorContext(activityDescriptor, type => CreateActivity(activityDescriptor, type, eleme
 22    }
 23
 24    public static T CreateActivity<T>(ActivityDescriptor activityDescriptor, JsonElement element, JsonSerializerOptions 
 25    {
 726        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
 260232        var raw = element.GetRawText();
 33
 34        // 2) Parse into a JsonNode so we can mutate
 260235        var node = JsonNode.Parse(raw)!;
 36
 37        // 3) Strip out any "_type" metadata wrappers
 260238        StripTypeMetadata(node);
 39
 40        // 4) Get a cleaned JSON string
 260241        var cleanedJson = node.ToJsonString(new(JsonSerializerDefaults.Web));
 42
 43        // 5) Parse it back to a JsonDocument so we can get a clean JsonElement
 260244        using var doc = JsonDocument.Parse(cleanedJson);
 260245        var cleanedElement = doc.RootElement.Clone();
 46
 47        // 6) Deserialize into IActivity, or create a plain instance
 260248        var activity = (IActivity)JsonSerializer.Deserialize(cleanedJson, type, serializerOptions)!;
 49
 50        // 7) Pull out your boolean flags from the cleaned element
 260251        var canStartWorkflow = GetBoolean(cleanedElement, "canStartWorkflow");
 260252        var runAsynchronously = GetNullableBoolean(cleanedElement, "runAsynchronously");
 260253        if (runAsynchronously is null)
 54        {
 11655            if (activityDescriptor.Attributes.OfType<TaskActivityAttribute>().FirstOrDefault() is { } taskActivityAttrib
 56            {
 057                runAsynchronously = taskActivityAttribute.RunAsynchronously;
 58            }
 59            else
 60            {
 11661                runAsynchronously = false;
 62            }
 63        }
 64
 65        // 8) If composite, setup
 260266        if (activity is IComposite composite)
 52067            composite.Setup();
 68
 69        // 9) Your existing synthetic inputs/outputs routines, using the cleanedElement
 260270        ReadSyntheticInputs(activityDescriptor, activity, cleanedElement, serializerOptions);
 260271        ReadSyntheticOutputs(activityDescriptor, activity, cleanedElement);
 72
 73        // 10) Finally re‑apply those flags
 260274        activity.SetCanStartWorkflow(canStartWorkflow);
 260275        activity.SetRunAsynchronously(runAsynchronously);
 76
 260277        return activity;
 260278    }
 79
 80    /// <summary>
 81    /// Recursively remove any "_type" properties and unwrap any
 82    /// { "_type": "...", "items": [ ... ] } or "values": [ ... ] wrappers
 83    /// </summary>
 84    private static void StripTypeMetadata(JsonNode node)
 85    {
 86        switch (node)
 87        {
 88            case JsonObject obj:
 89                // First recurse into each child
 36010890                foreach (var key in obj.Select(kvp => kvp.Key).ToList())
 91                {
 9876292                    var child = obj[key];
 9876293                    if (child is JsonObject wrapper && wrapper.ContainsKey("_type"))
 94                    {
 95                        // If it has an "items" array, replace the whole property with that array
 096                        if (wrapper["items"] is JsonArray items)
 97                        {
 098                            obj[key] = items;
 099                            StripTypeMetadata(items);
 0100                            continue;
 101                        }
 102
 103                        // Or a "values" array
 0104                        if (wrapper["values"] is JsonArray values)
 105                        {
 0106                            obj[key] = values;
 0107                            StripTypeMetadata(values);
 0108                            continue;
 109                        }
 110                    }
 111
 112                    // Otherwise just recurse normally
 98762113                    if (child != null)
 91960114                        StripTypeMetadata(child);
 115                }
 116
 117                // And remove any stray _type on this object
 31911118                obj.Remove("_type");
 31911119                break;
 120
 121            case JsonArray arr:
 10380122                for (var i = 0; i < arr.Count; i++)
 123                {
 2005124                    var element = arr[i];
 2005125                    if (element is JsonObject w && w.ContainsKey("_type"))
 126                    {
 0127                        if (w["items"] is JsonArray items)
 128                        {
 0129                            arr[i] = items;
 0130                            StripTypeMetadata(items);
 0131                            continue;
 132                        }
 133
 0134                        if (w["values"] is JsonArray values)
 135                        {
 0136                            arr[i] = values;
 0137                            StripTypeMetadata(values);
 0138                            continue;
 139                        }
 140                    }
 141
 2005142                    if (element != null)
 2005143                        StripTypeMetadata(element);
 144                }
 145
 146                break;
 147
 148            // primitives—nothing to do
 149            default:
 150                break;
 151        }
 3185152    }
 153
 154    private static void ReadSyntheticInputs(ActivityDescriptor activityDescriptor, IActivity activity, JsonElement activ
 155    {
 10062156        foreach (var inputDescriptor in activityDescriptor.Inputs.Where(x => x.IsSynthetic))
 157        {
 15158            var inputName = inputDescriptor.Name;
 15159            var propertyName = inputName.Camelize();
 15160            var nakedType = inputDescriptor.Type;
 15161            var wrappedType = typeof(Input<>).MakeGenericType(nakedType);
 162
 15163            if (!activityRoot.TryGetProperty(propertyName, out var propertyElement) || propertyElement.ValueKind == Json
 164                continue;
 165
 15166            var isWrapped = propertyElement.ValueKind == JsonValueKind.Object && propertyElement.GetProperty("typeName")
 167
 15168            if (isWrapped)
 169            {
 15170                var json = propertyElement.ToString();
 15171                var inputValue = JsonSerializer.Deserialize(json, wrappedType, options);
 172
 15173                activity.SyntheticProperties[inputName] = inputValue!;
 174            }
 175            else
 176            {
 0177                activity.SyntheticProperties[inputName] = propertyElement.ConvertTo(inputDescriptor.Type)!;
 178            }
 179        }
 2602180    }
 181
 182    private static void ReadSyntheticOutputs(ActivityDescriptor activityDescriptor, IActivity activity, JsonElement acti
 183    {
 7325184        foreach (var outputDescriptor in activityDescriptor.Outputs.Where(x => x.IsSynthetic))
 185        {
 49186            var outputName = outputDescriptor.Name;
 49187            var propertyName = outputName.Camelize();
 49188            var nakedType = outputDescriptor.Type;
 49189            var wrappedType = typeof(Output<>).MakeGenericType(nakedType);
 190
 49191            if (!activityRoot.TryGetProperty(propertyName, out var propertyElement) || propertyElement.ValueKind == Json
 192                continue;
 193
 15194            var memoryReferenceElement = propertyElement.GetProperty("memoryReference");
 195
 15196            if (!memoryReferenceElement.TryGetProperty("id", out var memoryReferenceIdElement))
 197                continue;
 198
 15199            var variable = new Variable
 15200            {
 15201                Id = memoryReferenceIdElement.GetString()!
 15202            };
 15203            variable.Name = variable.Id;
 204
 15205            var output = Activator.CreateInstance(wrappedType, variable)!;
 206
 15207            activity.SyntheticProperties[outputName] = output!;
 208        }
 2602209    }
 210
 211    private static bool GetBoolean(JsonElement element, string propertyName)
 212    {
 2602213        return GetNullableBoolean(element, propertyName) ?? false;
 214    }
 215
 216    private static bool? GetNullableBoolean(JsonElement element, string propertyName)
 217    {
 5204218        var propertyNames = new[]
 5204219        {
 5204220            propertyName.Camelize(), propertyName.Pascalize()
 5204221        };
 222
 16308223        foreach (var name in propertyNames)
 224        {
 5436225            if (element.TryGetProperty("customProperties", out var customPropertyElement))
 226            {
 5004227                if (customPropertyElement.TryGetProperty(name, out var canStartWorkflowElement))
 4972228                    return (bool?)canStartWorkflowElement.GetValue();
 229            }
 230
 464231            if (element.TryGetProperty(propertyName.Camelize(), out var property)
 464232                && (bool?)property.GetValue() is { } propValue)
 0233                return propValue;
 234        }
 235
 232236        return null;
 237    }
 238}