< Summary

Information
Class: Elsa.Workflows.Serialization.Converters.ActivityJsonConverter
Assembly: Elsa.Workflows.Core
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Core/Serialization/Converters/ActivityJsonConverter.cs
Line coverage
88%
Covered lines: 72
Uncovered lines: 9
Coverable lines: 81
Total lines: 176
Line coverage: 88.8%
Branch coverage
76%
Covered branches: 35
Total branches: 46
Branch coverage: 76%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Read(...)75%121290%
LogExceptionsIfAny(...)25%9433.33%
Write(...)66.66%66100%
GetActivityDetails(...)85%202091.3%
FindActivityDescriptorByCustomProperty(...)75%4475%
GetClonedOptions(...)100%11100%
GetClonedWriterOptions(...)100%11100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Core/Serialization/Converters/ActivityJsonConverter.cs

#LineLine coverage
 1using System.Text.Json;
 2using System.Text.Json.Serialization;
 3using Elsa.Expressions.Contracts;
 4using Elsa.Extensions;
 5using Elsa.Workflows.Activities;
 6using Elsa.Workflows.Helpers;
 7using Elsa.Workflows.Models;
 8using Elsa.Workflows.Serialization.Helpers;
 9using Microsoft.Extensions.DependencyInjection;
 10using Microsoft.Extensions.Logging;
 11
 12namespace Elsa.Workflows.Serialization.Converters;
 13
 14/// <summary>
 15/// (De)serializes objects of type <see cref="IActivity"/>.
 16/// </summary>
 584517public class ActivityJsonConverter(
 584518    IActivityRegistry activityRegistry,
 584519    IExpressionDescriptorRegistry expressionDescriptorRegistry,
 584520    ActivityWriter activityWriter,
 584521    IServiceProvider serviceProvider)
 22    : JsonConverter<IActivity>
 23{
 24    /// <inheritdoc />
 25    public override IActivity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
 26    {
 508327        if (!JsonDocument.TryParseValue(ref reader, out var doc))
 028            throw new JsonException("Failed to parse JsonDocument");
 29
 508330        var activityRoot = doc.RootElement;
 508331        var activityTypeName = GetActivityDetails(activityRoot, out var activityTypeVersion, out var activityDescriptor)
 508332        var notFoundActivityTypeName = ActivityTypeNameHelper.GenerateTypeName<NotFoundActivity>();
 33
 34        // If the activity type is a NotFoundActivity, try to extract the original activity type name and version.
 508335        if (activityTypeName.Equals(notFoundActivityTypeName) && activityRoot.TryGetProperty("originalActivityJson", out
 36        {
 037            activityRoot = JsonDocument.Parse(originalActivityJson.GetString()!).RootElement;
 038            activityTypeName = GetActivityDetails(activityRoot, out activityTypeVersion, out activityDescriptor);
 39        }
 40
 508341        var clonedOptions = GetClonedOptions(options);
 42        // If the activity type is not found, create a NotFoundActivity instead.
 508343        if (activityDescriptor == null)
 44        {
 17245            var notFoundActivityDescriptor = activityRegistry.Find<NotFoundActivity>()!;
 17246            var notFoundActivityResult = JsonActivityConstructorContextHelper.CreateActivity<NotFoundActivity>(notFoundA
 17247            LogExceptionsIfAny(notFoundActivityResult);
 48
 17249            var notFoundActivity = notFoundActivityResult.Activity;
 17250            notFoundActivity.Type = notFoundActivityTypeName;
 17251            notFoundActivity.Version = 1;
 17252            notFoundActivity.MissingTypeName = activityTypeName;
 17253            notFoundActivity.MissingTypeVersion = activityTypeVersion;
 17254            notFoundActivity.OriginalActivityJson = activityRoot.ToString();
 55
 56            // Extract metadata from doc.RootElement rather than activityRoot.
 57            // In round-trip scenarios, activityRoot may have been reassigned to the inner originalActivityJson (see lin
 58            // but we want the metadata from the current activity being deserialized, which represents the NotFoundActiv
 59            // placeholder's position and annotations in the designer.
 17260            if (doc.RootElement.TryGetProperty("metadata", out var outerMetadataElement))
 61            {
 17262                var outerMetadata = JsonSerializer.Deserialize<IDictionary<string, object>>(outerMetadataElement.GetRawT
 17263                if (outerMetadata != null)
 64                {
 17265                    notFoundActivity.Metadata = outerMetadata;
 66                }
 67            }
 68
 69            // Set display text and description after metadata assignment to ensure they always reflect the current stat
 17270            notFoundActivity.SetDisplayText($"Not Found: {activityTypeName}");
 17271            notFoundActivity.SetDescription($"Could not find activity type {activityTypeName} with version {activityType
 72
 17273            return notFoundActivity;
 74        }
 75
 491176        var context = JsonActivityConstructorContextHelper.Create(activityDescriptor, activityRoot, clonedOptions);
 491177        var activityResult = activityDescriptor.Constructor(context);
 491178        LogExceptionsIfAny(activityResult);
 79
 491180        return activityResult.Activity;
 81    }
 82
 83    void LogExceptionsIfAny(ActivityConstructionResult result)
 84    {
 508385        if (!result.HasExceptions)
 508386            return;
 87
 088        var logger = serviceProvider.GetRequiredService<ILogger<ActivityJsonConverter>>();
 089        foreach (var exception in result.Exceptions)
 090            logger.LogWarning("An exception was thrown while constructing activity with id '{activityId}': {Message}", r
 091    }
 92
 93    /// <inheritdoc />
 94    public override void Write(Utf8JsonWriter writer, IActivity value, JsonSerializerOptions options)
 95    {
 548696        var clonedOptions = GetClonedWriterOptions(options);
 548697        var activityDescriptor = activityRegistry.Find(value.Type, value.Version);
 98
 99        // Give the activity descriptor a chance to customize the serializer options.
 5486100        clonedOptions = activityDescriptor?.ConfigureSerializerOptions?.Invoke(clonedOptions) ?? clonedOptions;
 101
 5486102        activityWriter.WriteActivity(writer, value, clonedOptions);
 5486103    }
 104
 105    private string GetActivityDetails(JsonElement activityRoot, out int activityTypeVersion, out ActivityDescriptor? act
 106    {
 5083107        if (!activityRoot.TryGetProperty("type", out var activityTypeNameElement))
 0108            throw new JsonException("Failed to extract activity type property");
 109
 5083110        var activityTypeName = activityTypeNameElement.GetString()!;
 5083111        activityDescriptor = null;
 5083112        activityTypeVersion = 0;
 113
 114        // First, we check whether the activity type name is a 'well-known' activity; not a workflow-as-activity
 115
 116        // If the activity type version is specified, use that to find the activity descriptor.
 5083117        if (activityRoot.TryGetProperty("version", out var activityVersionElement))
 118        {
 5078119            activityTypeVersion = activityVersionElement.GetInt32();
 5078120            activityDescriptor = activityRegistry.Find(activityTypeName, activityTypeVersion);
 121        }
 122
 123        // If a version is not specified, or activity with specified version is not found: use the latest version of the
 5083124        if (activityDescriptor == null)
 125        {
 206126            activityDescriptor = activityRegistry.Find(activityTypeName);
 206127            activityTypeVersion = activityDescriptor?.Version ?? 0;
 128        }
 129
 130        // This is a special case when working with the WorkflowDefinitionActivity: workflowDefinitionVersionId should b
 5083131        if (activityRoot.TryGetProperty("workflowDefinitionVersionId", out var workflowDefinitionVersionIdElement))
 132        {
 882133            var activityDescriptorOverride = FindActivityDescriptorByCustomProperty("WorkflowDefinitionVersionId", workf
 882134            if (activityDescriptorOverride is null)
 199135                return activityTypeName;
 136
 683137            activityDescriptor = activityDescriptorOverride;
 683138            activityTypeVersion = activityDescriptor.Version;
 139        }
 140        // This is also a special case when working with the WorkflowDefinitionActivity: if no 'well-known' activity cou
 4201141        else if (activityDescriptor is null
 4201142                 && activityRoot.TryGetProperty("workflowDefinitionId", out var workflowDefinitionIdElement)
 4201143                 && workflowDefinitionIdElement.ValueKind == JsonValueKind.String)
 144        {
 1145            activityDescriptor = FindActivityDescriptorByCustomProperty("WorkflowDefinitionId", workflowDefinitionIdElem
 1146            activityTypeVersion = activityDescriptor?.Version ?? 0;
 147        }
 148
 4884149        return activityTypeName;
 150    }
 151
 152    private ActivityDescriptor? FindActivityDescriptorByCustomProperty(string customPropertyName, JsonElement valueEleme
 153    {
 883154        if (valueElement.ValueKind != JsonValueKind.String)
 0155            return null;
 156
 883157        var searchValue = valueElement.GetString();
 20101158        return activityRegistry.Find(x => x.CustomProperties.TryGetValue(customPropertyName, out var value) && (string?)
 159    }
 160
 161    private JsonSerializerOptions GetClonedOptions(JsonSerializerOptions options)
 162    {
 10569163        var clonedOptions = new JsonSerializerOptions(options);
 10569164        clonedOptions.Converters.Add(new InputJsonConverterFactory(serviceProvider));
 10569165        clonedOptions.Converters.Add(new OutputJsonConverterFactory(serviceProvider));
 10569166        clonedOptions.Converters.Add(new ExpressionJsonConverterFactory(expressionDescriptorRegistry));
 10569167        return clonedOptions;
 168    }
 169
 170    private JsonSerializerOptions GetClonedWriterOptions(JsonSerializerOptions options)
 171    {
 5486172        var clonedOptions = GetClonedOptions(options);
 5486173        clonedOptions.Converters.Add(new JsonIgnoreCompositeRootConverterFactory(serviceProvider.GetRequiredService<Acti
 5486174        return clonedOptions;
 175    }
 176}