< 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
92%
Covered lines: 82
Uncovered lines: 7
Coverable lines: 89
Total lines: 193
Line coverage: 92.1%
Branch coverage
82%
Covered branches: 41
Total branches: 50
Branch coverage: 82%
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(...)93.75%161697.36%
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>
 494617public class ActivityJsonConverter(
 494618    IActivityRegistry activityRegistry,
 494619    IExpressionDescriptorRegistry expressionDescriptorRegistry,
 494620    ActivityWriter activityWriter,
 494621    IServiceProvider serviceProvider)
 22    : JsonConverter<IActivity>
 23{
 24    /// <inheritdoc />
 25    public override IActivity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
 26    {
 376827        if (!JsonDocument.TryParseValue(ref reader, out var doc))
 028            throw new JsonException("Failed to parse JsonDocument");
 29
 376830        using (doc)
 31        {
 376832            JsonDocument? originalActivityDoc = null;
 33
 34            try
 35            {
 376836                var activityRoot = doc.RootElement;
 376837                var activityTypeName = GetActivityDetails(activityRoot, out var activityTypeVersion, out var activityDes
 376838                var notFoundActivityTypeName = ActivityTypeNameHelper.GenerateTypeName<NotFoundActivity>();
 39
 40                // If the activity type is a NotFoundActivity, try to extract the original activity type name and versio
 376841                if (activityTypeName.Equals(notFoundActivityTypeName) && activityRoot.TryGetProperty("originalActivityJs
 42                {
 143                    originalActivityDoc = JsonDocument.Parse(originalActivityJson.GetString()!);
 144                    activityRoot = originalActivityDoc.RootElement;
 145                    activityTypeName = GetActivityDetails(activityRoot, out activityTypeVersion, out activityDescriptor)
 46                }
 47
 376848                var clonedOptions = GetClonedOptions(options);
 49                // If the activity type is not found, create a NotFoundActivity instead.
 376850                if (activityDescriptor == null)
 51                {
 17452                    var notFoundActivityDescriptor = activityRegistry.Find<NotFoundActivity>();
 53
 17454                    if (notFoundActivityDescriptor == null)
 155                        throw new InvalidOperationException($"Unable to deserialize activity type '{activityTypeName}' b
 56
 17357                    var notFoundActivityResult = JsonActivityConstructorContextHelper.CreateActivity<NotFoundActivity>(n
 17358                    LogExceptionsIfAny(notFoundActivityResult);
 59
 17360                    var notFoundActivity = notFoundActivityResult.Activity;
 17361                    notFoundActivity.Type = notFoundActivityTypeName;
 17362                    notFoundActivity.Version = 1;
 17363                    notFoundActivity.MissingTypeName = activityTypeName;
 17364                    notFoundActivity.MissingTypeVersion = activityTypeVersion;
 17365                    notFoundActivity.OriginalActivityJson = activityRoot.ToString();
 66
 67                    // Extract metadata from doc.RootElement rather than activityRoot.
 68                    // In round-trip scenarios, activityRoot may have been reassigned to the inner originalActivityJson 
 69                    // but we want the metadata from the current activity being deserialized, which represents the NotFo
 70                    // placeholder's position and annotations in the designer.
 17371                    if (doc.RootElement.TryGetProperty("metadata", out var outerMetadataElement))
 72                    {
 17373                        var outerMetadata = JsonSerializer.Deserialize<IDictionary<string, object>>(outerMetadataElement
 17374                        if (outerMetadata != null)
 75                        {
 17376                            notFoundActivity.Metadata = outerMetadata;
 77                        }
 78                    }
 79
 80                    // Set display text and description after metadata assignment to ensure they always reflect the curr
 17381                    notFoundActivity.SetDisplayText($"Not Found: {activityTypeName}");
 17382                    notFoundActivity.SetDescription($"Could not find activity type {activityTypeName} with version {acti
 83
 17384                    return notFoundActivity;
 85                }
 86
 359487                var context = JsonActivityConstructorContextHelper.Create(activityDescriptor, activityRoot, clonedOption
 359488                var activityResult = activityDescriptor.Constructor(context);
 359489                LogExceptionsIfAny(activityResult);
 90
 359491                return activityResult.Activity;
 92            }
 93            finally
 94            {
 376895                originalActivityDoc?.Dispose();
 376896            }
 97        }
 376798    }
 99
 100    void LogExceptionsIfAny(ActivityConstructionResult result)
 101    {
 3767102        if (!result.HasExceptions)
 3767103            return;
 104
 0105        var logger = serviceProvider.GetRequiredService<ILogger<ActivityJsonConverter>>();
 0106        foreach (var exception in result.Exceptions)
 0107            logger.LogWarning("An exception was thrown while constructing activity with id '{activityId}': {Message}", r
 0108    }
 109
 110    /// <inheritdoc />
 111    public override void Write(Utf8JsonWriter writer, IActivity value, JsonSerializerOptions options)
 112    {
 5559113        var clonedOptions = GetClonedWriterOptions(options);
 5559114        var activityDescriptor = activityRegistry.Find(value.Type, value.Version);
 115
 116        // Give the activity descriptor a chance to customize the serializer options.
 5559117        clonedOptions = activityDescriptor?.ConfigureSerializerOptions?.Invoke(clonedOptions) ?? clonedOptions;
 118
 5559119        activityWriter.WriteActivity(writer, value, clonedOptions);
 5559120    }
 121
 122    private string GetActivityDetails(JsonElement activityRoot, out int activityTypeVersion, out ActivityDescriptor? act
 123    {
 3769124        if (!activityRoot.TryGetProperty("type", out var activityTypeNameElement))
 0125            throw new JsonException("Failed to extract activity type property");
 126
 3769127        var activityTypeName = activityTypeNameElement.GetString()!;
 3769128        activityDescriptor = null;
 3769129        activityTypeVersion = 0;
 130
 131        // First, we check whether the activity type name is a 'well-known' activity; not a workflow-as-activity
 132
 133        // If the activity type version is specified, use that to find the activity descriptor.
 3769134        if (activityRoot.TryGetProperty("version", out var activityVersionElement))
 135        {
 3762136            activityTypeVersion = activityVersionElement.GetInt32();
 3762137            activityDescriptor = activityRegistry.Find(activityTypeName, activityTypeVersion);
 138        }
 139
 140        // If a version is not specified, or activity with specified version is not found: use the latest version of the
 3769141        if (activityDescriptor == null)
 142        {
 208143            activityDescriptor = activityRegistry.Find(activityTypeName);
 208144            activityTypeVersion = activityDescriptor?.Version ?? 0;
 145        }
 146
 147        // This is a special case when working with the WorkflowDefinitionActivity: workflowDefinitionVersionId should b
 3769148        if (activityRoot.TryGetProperty("workflowDefinitionVersionId", out var workflowDefinitionVersionIdElement))
 149        {
 514150            var activityDescriptorOverride = FindActivityDescriptorByCustomProperty("WorkflowDefinitionVersionId", workf
 514151            if (activityDescriptorOverride is null)
 199152                return activityTypeName;
 153
 315154            activityDescriptor = activityDescriptorOverride;
 315155            activityTypeVersion = activityDescriptor.Version;
 156        }
 157        // This is also a special case when working with the WorkflowDefinitionActivity: if no 'well-known' activity cou
 3255158        else if (activityDescriptor is null
 3255159                 && activityRoot.TryGetProperty("workflowDefinitionId", out var workflowDefinitionIdElement)
 3255160                 && workflowDefinitionIdElement.ValueKind == JsonValueKind.String)
 161        {
 1162            activityDescriptor = FindActivityDescriptorByCustomProperty("WorkflowDefinitionId", workflowDefinitionIdElem
 1163            activityTypeVersion = activityDescriptor?.Version ?? 0;
 164        }
 165
 3570166        return activityTypeName;
 167    }
 168
 169    private ActivityDescriptor? FindActivityDescriptorByCustomProperty(string customPropertyName, JsonElement valueEleme
 170    {
 515171        if (valueElement.ValueKind != JsonValueKind.String)
 0172            return null;
 173
 515174        var searchValue = valueElement.GetString();
 19365175        return activityRegistry.Find(x => x.CustomProperties.TryGetValue(customPropertyName, out var value) && (string?)
 176    }
 177
 178    private JsonSerializerOptions GetClonedOptions(JsonSerializerOptions options)
 179    {
 9327180        var clonedOptions = new JsonSerializerOptions(options);
 9327181        clonedOptions.Converters.Add(new InputJsonConverterFactory(serviceProvider));
 9327182        clonedOptions.Converters.Add(new OutputJsonConverterFactory(serviceProvider));
 9327183        clonedOptions.Converters.Add(new ExpressionJsonConverterFactory(expressionDescriptorRegistry));
 9327184        return clonedOptions;
 185    }
 186
 187    private JsonSerializerOptions GetClonedWriterOptions(JsonSerializerOptions options)
 188    {
 5559189        var clonedOptions = GetClonedOptions(options);
 5559190        clonedOptions.Converters.Add(new JsonIgnoreCompositeRootConverterFactory(serviceProvider.GetRequiredService<Acti
 5559191        return clonedOptions;
 192    }
 193}