Skip to main content
Welcome. This site supports keyboard navigation and screen readers. Press ? at any time for keyboard shortcuts. Press [ to focus the sidebar, ] to focus the content. High-contrast themes are available via the toolbar.
serard@dev00:~/cv

The Merge Algorithm

internal static class SchemaVersionMerger
{
    public static UnifiedSchema Merge(List<SchemaModel> schemas)
    {
        if (schemas.Count == 0)
            return new UnifiedSchema();

        // Step 1: Sort by semantic version
        schemas.Sort((a, b) => CompareVersions(a.Version, b.Version));

        var unified = new UnifiedSchema
        {
            Versions = schemas.Select(s => s.Version).ToList()
        };

        var firstVersion = schemas[0].Version;
        var lastVersion = schemas[schemas.Count - 1].Version;

        // Step 2: Merge definitions across versions
        var allDefNames = schemas
            .SelectMany(s => s.Definitions.Keys)
            .Distinct().ToList();

        foreach (var defName in allDefNames)
        {
            var firstAppearance = schemas
                .FirstOrDefault(s => s.Definitions.ContainsKey(defName));
            var lastAppearance = schemas
                .LastOrDefault(s => s.Definitions.ContainsKey(defName));
            if (firstAppearance is null) continue;

            // Use latest version's definition (most complete)
            var latestDef = schemas
                .Last(s => s.Definitions.ContainsKey(defName))
                .Definitions[defName];

            var uniDef = new UnifiedDefinition
            {
                Name = defName,
                Description = latestDef.Description,
                SinceVersion = firstAppearance.Version == firstVersion
                    ? null : firstAppearance.Version,
                UntilVersion = lastAppearance!.Version == lastVersion
                    ? null : lastAppearance.Version
            };

            // Step 3: Merge properties within each definition
            var allPropNames = schemas
                .Where(s => s.Definitions.ContainsKey(defName))
                .SelectMany(s => s.Definitions[defName].Properties
                    .Select(p => p.JsonName))
                .Distinct().ToList();

            foreach (var propName in allPropNames)
            {
                var firstPropVersion = schemas.FirstOrDefault(s =>
                    s.Definitions.ContainsKey(defName) &&
                    s.Definitions[defName].Properties
                        .Any(p => p.JsonName == propName));
                var lastPropVersion = schemas.LastOrDefault(s =>
                    s.Definitions.ContainsKey(defName) &&
                    s.Definitions[defName].Properties
                        .Any(p => p.JsonName == propName));

                if (firstPropVersion is null) continue;

                // Use latest property definition
                var latestProp = schemas
                    .Last(s => s.Definitions.ContainsKey(defName) &&
                        s.Definitions[defName].Properties
                            .Any(p => p.JsonName == propName))
                    .Definitions[defName].Properties
                        .First(p => p.JsonName == propName);

                uniDef.Properties.Add(new UnifiedProperty
                {
                    Property = latestProp,
                    SinceVersion = firstPropVersion.Version == firstVersion
                        ? null : firstPropVersion.Version,
                    UntilVersion = lastPropVersion!.Version == lastVersion
                        ? null : lastPropVersion.Version
                });
            }

            unified.Definitions[defName] = uniDef;
        }

        // Step 4: Merge root properties (same algorithm)
        var allRootProps = schemas
            .SelectMany(s => s.RootProperties.Select(p => p.JsonName))
            .Distinct().ToList();

        foreach (var propName in allRootProps)
        {
            var firstPropVersion = schemas
                .FirstOrDefault(s => s.RootProperties
                    .Any(p => p.JsonName == propName));
            var lastPropVersion = schemas
                .LastOrDefault(s => s.RootProperties
                    .Any(p => p.JsonName == propName));
            if (firstPropVersion is null) continue;

            var latestProp = schemas
                .Last(s => s.RootProperties.Any(p => p.JsonName == propName))
                .RootProperties.First(p => p.JsonName == propName);

            unified.RootProperties.Add(new UnifiedProperty
            {
                Property = latestProp,
                SinceVersion = firstPropVersion.Version == firstVersion
                    ? null : firstPropVersion.Version,
                UntilVersion = lastPropVersion!.Version == lastVersion
                    ? null : lastPropVersion.Version
            });
        }

        return unified;
    }
}

Version Tracking Logic

The merger assigns version metadata based on three rules:

  1. SinceVersion = null when a property exists in the first schema version (18.0.0). This means "always available" — no annotation needed.
  2. SinceVersion = "X.Y.Z" when a property first appears in a later version. For example, Inputs on jobs first appears in 18.6.0, so it gets [SinceVersion("18.6.0")].
  3. UntilVersion = "X.Y.Z" when a property disappears before the latest version. This is rare but handled for completeness.
Diagram
Concrete example of the three version rules — script is always available, while run, inputs, and manual_confirmation each inherit a SinceVersion marker from the minor that first declared them.

Version Comparison

internal static int CompareVersions(string a, string b)
{
    var partsA = a.Split('.');
    var partsB = b.Split('.');
    for (var i = 0; i < System.Math.Max(partsA.Length, partsB.Length); i++)
    {
        var va = i < partsA.Length && int.TryParse(partsA[i], out var ia) ? ia : 0;
        var vb = i < partsB.Length && int.TryParse(partsB[i], out var ib) ? ib : 0;
        if (va != vb) return va.CompareTo(vb);
    }
    return 0;
}

Unified Schema Model

The output of the merger:

internal sealed class UnifiedSchema
{
    public List<string> Versions { get; set; } = new();
    public Dictionary<string, UnifiedDefinition> Definitions { get; set; } = new();
    public List<UnifiedProperty> RootProperties { get; set; } = new();
}

internal sealed class UnifiedDefinition
{
    public string Name { get; set; } = "";
    public string? Description { get; set; }
    public List<UnifiedProperty> Properties { get; set; } = new();
    public string? SinceVersion { get; set; }
    public string? UntilVersion { get; set; }
}

internal sealed class UnifiedProperty
{
    public PropertyModel Property { get; set; } = null!;
    public string? SinceVersion { get; set; }
    public string? UntilVersion { get; set; }
}

This design means every property in the generated code carries its version range. Consumers can use reflection to check [SinceVersion] and [UntilVersion] attributes at runtime, or simply use IntelliSense to see version annotations in XML docs.


⬇ Download