< Summary

Information
Class: Elsa.Workflows.Runtime.Comparers.WorkflowTriggerEqualityComparer
Assembly: Elsa.Workflows.Runtime
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Runtime/Comparers/WorkflowTriggerEqualityComparer.cs
Line coverage
100%
Covered lines: 37
Uncovered lines: 0
Coverable lines: 37
Total lines: 94
Line coverage: 100%
Branch coverage
66%
Covered branches: 4
Total branches: 6
Branch coverage: 66.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
Equals(...)50%44100%
GetHashCode(...)100%11100%
Serialize(...)100%11100%
NormalizePayload(...)100%22100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Workflows.Runtime/Comparers/WorkflowTriggerEqualityComparer.cs

#LineLine coverage
 1using System.Text.Json;
 2using System.Text.Json.Serialization;
 3using System.Text.Json.Serialization.Metadata;
 4using Elsa.Expressions.Services;
 5using Elsa.Workflows.Runtime.Entities;
 6using Elsa.Workflows.Serialization.Converters;
 7
 8namespace Elsa.Workflows.Runtime.Comparers;
 9
 10/// <summary>
 11/// Compares two <see cref="StoredTrigger"/> instances for equality.
 12/// </summary>
 13public class WorkflowTriggerEqualityComparer : IEqualityComparer<StoredTrigger>
 14{
 15    private readonly JsonSerializerOptions _settings;
 16
 17    /// <summary>
 18    /// Initializes a new instance of the <see cref="WorkflowTriggerEqualityComparer"/> class.
 19    /// </summary>
 148720    public WorkflowTriggerEqualityComparer()
 21    {
 148722        _settings = new()
 148723        {
 148724            // Enables serialization of ValueTuples, which use fields instead of properties.
 148725            IncludeFields = true,
 148726            PropertyNameCaseInsensitive = true,
 148727            // Use camelCase to match IPayloadSerializer, which stores payloads using camelCase.
 148728            // Without this, fresh payloads (typed CLR objects) serialize with PascalCase while
 148729            // DB-loaded payloads (JsonElement) preserve their stored camelCase keys, causing
 148730            // the diff to always report all triggers as changed.
 148731            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
 148732            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
 148733        };
 34
 35        // Mirror the converters used by IPayloadSerializer so that enum, TimeSpan, and
 36        // polymorphic object properties serialize identically to their stored representation.
 148737        _settings.Converters.Add(new JsonStringEnumConverter());
 148738        _settings.Converters.Add(JsonMetadataServices.TimeSpanConverter);
 148739        _settings.Converters.Add(new PolymorphicObjectConverterFactory());
 148740        _settings.Converters.Add(new TypeJsonConverter(WellKnownTypeRegistry.CreateDefault()));
 148741    }
 42
 43    /// <inheritdoc />
 44    public bool Equals(StoredTrigger? x, StoredTrigger? y)
 45    {
 75446        var xJson = x != null ? Serialize(x) : null;
 75447        var yJson = y != null ? Serialize(y) : null;
 75448        return xJson == yJson;
 49    }
 50
 51    /// <inheritdoc />
 52    public int GetHashCode(StoredTrigger obj)
 53    {
 186054        var json = Serialize(obj);
 186055        return json.GetHashCode();
 56    }
 57
 58    private string Serialize(StoredTrigger storedTrigger)
 59    {
 60        // Normalize the payload to a canonical JSON string so that both typed CLR objects
 61        // and JsonElement instances (from DB round-trips) produce identical output.
 336862        var normalizedPayload = NormalizePayload(storedTrigger.Payload);
 63
 336864        var input = new
 336865        {
 336866            Payload = normalizedPayload,
 336867            storedTrigger.Name,
 336868            storedTrigger.ActivityId,
 336869            storedTrigger.WorkflowDefinitionId,
 336870            storedTrigger.WorkflowDefinitionVersionId,
 336871            storedTrigger.Hash
 336872        };
 336873        return JsonSerializer.Serialize(input, _settings);
 74    }
 75
 76    /// <summary>
 77    /// Normalizes a payload to a canonical JSON string representation.
 78    /// This ensures that typed CLR objects and JsonElements (which preserve their original
 79    /// property name casing from DB storage) produce identical output when compared.
 80    /// </summary>
 81    private string? NormalizePayload(object? payload)
 82    {
 336883        if (payload == null)
 484            return null;
 85
 86        // Serialize using the concrete runtime type rather than object, so that
 87        // PolymorphicObjectConverterFactory (which only handles typeof(object)) does not
 88        // inject a "_type" discriminator field into CLR payloads. Without this,
 89        // fresh CLR payloads produce {"path":"...","_type":"..."} while DB-loaded
 90        // JsonElement payloads produce {"path":"..."}, and the two never compare equal.
 336491        return JsonSerializer.Serialize(payload, payload.GetType(), _settings);
 92    }
 93}
 94