< Summary

Information
Class: Elsa.Bpmn.Interchange.Endpoints.Bpmn.Document.BpmnDocumentETag
Assembly: Elsa.Bpmn.Interchange
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Bpmn.Interchange/Endpoints/Bpmn/Document/BpmnDocumentETag.cs
Line coverage
100%
Covered lines: 8
Uncovered lines: 0
Coverable lines: 8
Total lines: 49
Line coverage: 100%
Branch coverage
50%
Covered branches: 1
Total branches: 2
Branch coverage: 50%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
From(...)50%22100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Bpmn.Interchange/Endpoints/Bpmn/Document/BpmnDocumentETag.cs

#LineLine coverage
 1using System.Globalization;
 2using System.Security.Cryptography;
 3using Elsa.Bpmn.Interchange.Services;
 4using Elsa.Extensions;
 5using Elsa.Workflows.Management.Entities;
 6
 7namespace Elsa.Bpmn.Interchange.Endpoints.Bpmn.Document;
 8
 9/// <summary>
 10/// The strong <c>ETag</c> the document <c>Get</c> and <c>Put</c> endpoints exchange for optimistic concurrency: a
 11/// SHA-256 hash over the stored workflow definition's id, its version, its BPMN source
 12/// (<see cref="BpmnInterchangeDocumentService.SourceXmlCustomPropertyKey"/>) and its serialized activity graph
 13/// (<see cref="WorkflowDefinition.StringData"/>).
 14/// </summary>
 15/// <remarks>
 16/// <para>
 17/// Content-derived rather than a stored counter, because no counter survives every writer. An unpublished draft is
 18/// edited in place — same row, same version — and both <see cref="Elsa.Workflows.Management.IWorkflowDefinitionImporter
 19/// (behind <c>Import</c> and the document <c>Put</c>) and the workflow-definition save endpoint the designer uses
 20/// replace <c>CustomProperties</c> wholesale with the caller's, so a counter kept there is wiped, or carried forward
 21/// unchanged, by exactly the writes it would have to record. What those writes do change is the content: a document
 22/// <c>Put</c> or an <c>Import</c> rewrites the stored source, a designer save rewrites the graph, and a save that drops
 23/// the source hashes differently from one that keeps it. The id and version tie the value to one stored row, so a new
 24/// draft version of identical content still gets a different one.
 25/// </para>
 26/// <para>
 27/// Identical stored content yields an identical <c>ETag</c>, which is what a strong validator means — it names a
 28/// representation — so a <c>Put</c> that writes back exactly what is stored returns the value <c>Get</c> did, having
 29/// overwritten nothing. Every input is length-prefixed and an absent one is marked distinctly from an empty one, so
 30/// no two different sets of inputs feed the hash the same bytes. The value is opaque to clients, which must send it
 31/// back verbatim.
 32/// </para>
 33/// </remarks>
 34internal static class BpmnDocumentETag
 35{
 36    /// <summary>Computes the quoted strong ETag for <paramref name="definition"/> as it is stored.</summary>
 37    public static string From(WorkflowDefinition definition)
 38    {
 16039        var sourceXml = definition.CustomProperties.TryGetValue<string>(BpmnInterchangeDocumentService.SourceXmlCustomPr
 40
 16041        using var hash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256);
 16042        BpmnContentHash.AppendField(hash, definition.Id);
 16043        BpmnContentHash.AppendField(hash, definition.Version.ToString(CultureInfo.InvariantCulture));
 16044        BpmnContentHash.AppendField(hash, sourceXml);
 16045        BpmnContentHash.AppendField(hash, definition.StringData);
 46
 16047        return $"\"{Convert.ToHexString(hash.GetHashAndReset())}\"";
 16048    }
 49}