Dispose parsed activity JsonDocuments (#7713)
* Dispose parsed activity JsonDocuments * Add ActivityJsonConverter disposal regression test * Fix ActivityJsonConverter disposal regression test file * address greptile test coverage feedback
This commit is contained in:
parent
ded765b379
commit
9c24f5efe5
|
|
@ -27,57 +27,70 @@ public class ActivityJsonConverter(
|
|||
if (!JsonDocument.TryParseValue(ref reader, out var doc))
|
||||
throw new JsonException("Failed to parse JsonDocument");
|
||||
|
||||
var activityRoot = doc.RootElement;
|
||||
var activityTypeName = GetActivityDetails(activityRoot, out var activityTypeVersion, out var activityDescriptor);
|
||||
var notFoundActivityTypeName = ActivityTypeNameHelper.GenerateTypeName<NotFoundActivity>();
|
||||
|
||||
// If the activity type is a NotFoundActivity, try to extract the original activity type name and version.
|
||||
if (activityTypeName.Equals(notFoundActivityTypeName) && activityRoot.TryGetProperty("originalActivityJson", out var originalActivityJson))
|
||||
using (doc)
|
||||
{
|
||||
activityRoot = JsonDocument.Parse(originalActivityJson.GetString()!).RootElement;
|
||||
activityTypeName = GetActivityDetails(activityRoot, out activityTypeVersion, out activityDescriptor);
|
||||
}
|
||||
JsonDocument? originalActivityDoc = null;
|
||||
|
||||
var clonedOptions = GetClonedOptions(options);
|
||||
// If the activity type is not found, create a NotFoundActivity instead.
|
||||
if (activityDescriptor == null)
|
||||
{
|
||||
var notFoundActivityDescriptor = activityRegistry.Find<NotFoundActivity>()!;
|
||||
var notFoundActivityResult = JsonActivityConstructorContextHelper.CreateActivity<NotFoundActivity>(notFoundActivityDescriptor, activityRoot, clonedOptions);
|
||||
LogExceptionsIfAny(notFoundActivityResult);
|
||||
|
||||
var notFoundActivity = notFoundActivityResult.Activity;
|
||||
notFoundActivity.Type = notFoundActivityTypeName;
|
||||
notFoundActivity.Version = 1;
|
||||
notFoundActivity.MissingTypeName = activityTypeName;
|
||||
notFoundActivity.MissingTypeVersion = activityTypeVersion;
|
||||
notFoundActivity.OriginalActivityJson = activityRoot.ToString();
|
||||
|
||||
// Extract metadata from doc.RootElement rather than activityRoot.
|
||||
// In round-trip scenarios, activityRoot may have been reassigned to the inner originalActivityJson (see line 37),
|
||||
// but we want the metadata from the current activity being deserialized, which represents the NotFoundActivity
|
||||
// placeholder's position and annotations in the designer.
|
||||
if (doc.RootElement.TryGetProperty("metadata", out var outerMetadataElement))
|
||||
try
|
||||
{
|
||||
var outerMetadata = JsonSerializer.Deserialize<IDictionary<string, object>>(outerMetadataElement.GetRawText(), clonedOptions);
|
||||
if (outerMetadata != null)
|
||||
var activityRoot = doc.RootElement;
|
||||
var activityTypeName = GetActivityDetails(activityRoot, out var activityTypeVersion, out var activityDescriptor);
|
||||
var notFoundActivityTypeName = ActivityTypeNameHelper.GenerateTypeName<NotFoundActivity>();
|
||||
|
||||
// If the activity type is a NotFoundActivity, try to extract the original activity type name and version.
|
||||
if (activityTypeName.Equals(notFoundActivityTypeName) && activityRoot.TryGetProperty("originalActivityJson", out var originalActivityJson))
|
||||
{
|
||||
notFoundActivity.Metadata = outerMetadata;
|
||||
originalActivityDoc = JsonDocument.Parse(originalActivityJson.GetString()!);
|
||||
activityRoot = originalActivityDoc.RootElement;
|
||||
activityTypeName = GetActivityDetails(activityRoot, out activityTypeVersion, out activityDescriptor);
|
||||
}
|
||||
|
||||
var clonedOptions = GetClonedOptions(options);
|
||||
// If the activity type is not found, create a NotFoundActivity instead.
|
||||
if (activityDescriptor == null)
|
||||
{
|
||||
var notFoundActivityDescriptor = activityRegistry.Find<NotFoundActivity>()!;
|
||||
var notFoundActivityResult = JsonActivityConstructorContextHelper.CreateActivity<NotFoundActivity>(notFoundActivityDescriptor, activityRoot, clonedOptions);
|
||||
LogExceptionsIfAny(notFoundActivityResult);
|
||||
|
||||
var notFoundActivity = notFoundActivityResult.Activity;
|
||||
notFoundActivity.Type = notFoundActivityTypeName;
|
||||
notFoundActivity.Version = 1;
|
||||
notFoundActivity.MissingTypeName = activityTypeName;
|
||||
notFoundActivity.MissingTypeVersion = activityTypeVersion;
|
||||
notFoundActivity.OriginalActivityJson = activityRoot.ToString();
|
||||
|
||||
// Extract metadata from doc.RootElement rather than activityRoot.
|
||||
// In round-trip scenarios, activityRoot may have been reassigned to the inner originalActivityJson (see line 42),
|
||||
// but we want the metadata from the current activity being deserialized, which represents the NotFoundActivity
|
||||
// placeholder's position and annotations in the designer.
|
||||
if (doc.RootElement.TryGetProperty("metadata", out var outerMetadataElement))
|
||||
{
|
||||
var outerMetadata = JsonSerializer.Deserialize<IDictionary<string, object>>(outerMetadataElement.GetRawText(), clonedOptions);
|
||||
if (outerMetadata != null)
|
||||
{
|
||||
notFoundActivity.Metadata = outerMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
// Set display text and description after metadata assignment to ensure they always reflect the current state
|
||||
notFoundActivity.SetDisplayText($"Not Found: {activityTypeName}");
|
||||
notFoundActivity.SetDescription($"Could not find activity type {activityTypeName} with version {activityTypeVersion}");
|
||||
|
||||
return notFoundActivity;
|
||||
}
|
||||
|
||||
var context = JsonActivityConstructorContextHelper.Create(activityDescriptor, activityRoot, clonedOptions);
|
||||
var activityResult = activityDescriptor.Constructor(context);
|
||||
LogExceptionsIfAny(activityResult);
|
||||
|
||||
return activityResult.Activity;
|
||||
}
|
||||
finally
|
||||
{
|
||||
originalActivityDoc?.Dispose();
|
||||
}
|
||||
|
||||
// Set display text and description after metadata assignment to ensure they always reflect the current state
|
||||
notFoundActivity.SetDisplayText($"Not Found: {activityTypeName}");
|
||||
notFoundActivity.SetDescription($"Could not find activity type {activityTypeName} with version {activityTypeVersion}");
|
||||
|
||||
return notFoundActivity;
|
||||
}
|
||||
|
||||
var context = JsonActivityConstructorContextHelper.Create(activityDescriptor, activityRoot, clonedOptions);
|
||||
var activityResult = activityDescriptor.Constructor(context);
|
||||
LogExceptionsIfAny(activityResult);
|
||||
|
||||
return activityResult.Activity;
|
||||
}
|
||||
|
||||
void LogExceptionsIfAny(ActivityConstructionResult result)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using Elsa.Common.Serialization;
|
||||
using Elsa.Expressions.Services;
|
||||
using Elsa.Workflows.Activities;
|
||||
|
|
@ -63,10 +63,31 @@ public sealed class ActivityJsonConverterTests
|
|||
var notFoundActivity = (NotFoundActivity)result;
|
||||
Assert.Equal(UnknownActivityTypeName, notFoundActivity.MissingTypeName);
|
||||
Assert.Equal(0, notFoundActivity.MissingTypeVersion);
|
||||
AssertEquivalentJson(UnknownActivityJson, notFoundActivity.OriginalActivityJson);
|
||||
Assert.True(notFoundActivity.Metadata.ContainsKey("displayText"));
|
||||
Assert.True(notFoundActivity.Metadata.ContainsKey("description"));
|
||||
}
|
||||
|
||||
var expectedJsonDoc = JsonDocument.Parse(UnknownActivityJson);
|
||||
var actualJsonDoc = JsonDocument.Parse(notFoundActivity.OriginalActivityJson);
|
||||
Assert.Equal(expectedJsonDoc.RootElement.ToString(), actualJsonDoc.RootElement.ToString());
|
||||
[Fact]
|
||||
public void When_DeserializeNestedNotFoundActivity_Then_PreservesMissingActivityBehavior()
|
||||
{
|
||||
// Arrange
|
||||
var activityRegistry = Substitute.For<IActivityRegistry>();
|
||||
activityRegistry
|
||||
.Find(NotFoundActivityTypeName)
|
||||
.Returns(new ActivityDescriptor());
|
||||
|
||||
var sut = CreateSut(activityRegistry);
|
||||
|
||||
// Act
|
||||
var result = Execute(sut, NestedNotFoundActivityJson);
|
||||
|
||||
// Assert
|
||||
var notFoundActivity = Assert.IsType<NotFoundActivity>(result);
|
||||
Assert.Equal(UnknownActivityTypeName, notFoundActivity.MissingTypeName);
|
||||
Assert.Equal(0, notFoundActivity.MissingTypeVersion);
|
||||
AssertEquivalentJson(UnknownActivityJson, notFoundActivity.OriginalActivityJson);
|
||||
Assert.True(notFoundActivity.Metadata.ContainsKey("outerMarker"));
|
||||
Assert.True(notFoundActivity.Metadata.ContainsKey("displayText"));
|
||||
Assert.True(notFoundActivity.Metadata.ContainsKey("description"));
|
||||
}
|
||||
|
|
@ -123,6 +144,13 @@ public sealed class ActivityJsonConverterTests
|
|||
static IActivity? Execute(ActivityJsonConverter sut, string json) =>
|
||||
JsonSerializer.Deserialize<IActivity>(json, GetSerializerOptions(sut));
|
||||
|
||||
static void AssertEquivalentJson(string expectedJson, string actualJson)
|
||||
{
|
||||
using var expectedJsonDoc = JsonDocument.Parse(expectedJson);
|
||||
using var actualJsonDoc = JsonDocument.Parse(actualJson);
|
||||
Assert.Equal(expectedJsonDoc.RootElement.ToString(), actualJsonDoc.RootElement.ToString());
|
||||
}
|
||||
|
||||
static IActivityRegistry CreateActivityRegistry(string typeName, IActivity activity, int? version = null)
|
||||
{
|
||||
var descriptor = new ActivityDescriptor { Constructor = _ => new(activity) };
|
||||
|
|
@ -177,6 +205,18 @@ public sealed class ActivityJsonConverterTests
|
|||
private static readonly WriteLine WriteLineActivity = new("Hello world!");
|
||||
private static readonly string WriteLineActivityTypeName = ActivityTypeNameHelper.GenerateTypeName<WriteLine>();
|
||||
private static readonly string NotFoundActivityTypeName = ActivityTypeNameHelper.GenerateTypeName<NotFoundActivity>();
|
||||
private static readonly string NestedNotFoundActivityJson =
|
||||
$$"""
|
||||
{
|
||||
"id": "wrapped-not-found",
|
||||
"type": "{{NotFoundActivityTypeName}}",
|
||||
"version": 1,
|
||||
"originalActivityJson": {{JsonSerializer.Serialize(UnknownActivityJson)}},
|
||||
"metadata": {
|
||||
"outerMarker": "preserved"
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private const string WriteLineActivityJson_WithVersion =
|
||||
"""
|
||||
|
|
|
|||
Loading…
Reference in a new issue