< Summary

Information
Class: NGql.Core.Extensions.QueryDefinitionExtensions
Assembly: NGql.Core
File(s): /home/runner/work/NGql/NGql/src/Core/Extensions/QueryDefinitionExtensions.cs
Line coverage
100%
Covered lines: 35
Uncovered lines: 0
Coverable lines: 35
Total lines: 109
Line coverage: 100%
Branch coverage
96%
Covered branches: 29
Total branches: 30
Branch coverage: 96.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
NavigatePath(...)100%88100%
WalkPath(...)100%1010100%
FindFieldRecursively(...)100%11100%
FindFieldRecursivelyCore(...)100%88100%
NameOrAliasMatches(...)75%44100%

File(s)

/home/runner/work/NGql/NGql/src/Core/Extensions/QueryDefinitionExtensions.cs

#LineLine coverage
 1using System.Runtime.CompilerServices;
 2using NGql.Core.Abstractions;
 3using NGql.Core.Features;
 4
 5namespace NGql.Core.Extensions;
 6
 7/// <summary>
 8/// Extension methods for QueryDefinition and field navigation/lookup utilities.
 9/// </summary>
 10internal static class QueryDefinitionExtensions
 11{
 12    /// <summary>
 13    /// Navigates through a dot-separated path using name or alias matching.
 14    /// Returns the final field and optionally builds the resolved path.
 15    /// Optimized with ReadOnlySpan to avoid allocations.
 16    /// </summary>
 17    /// <param name="fields">Starting field collection</param>
 18    /// <param name="path">Dot-separated path (can use ReadOnlySpan for zero-alloc)</param>
 19    /// <param name="resolvedPath">Optional: outputs the actual path with field keys (not aliases)</param>
 20    /// <param name="prependPath">Optional: path to prepend to resolvedPath</param>
 21    /// <returns>The final field definition, or null if not found</returns>
 22    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 23    internal static FieldDefinition? NavigatePath(
 24        IReadOnlyDictionary<string, FieldDefinition>? fields,
 25        ReadOnlySpan<char> path,
 26        out string? resolvedPath,
 27        string? prependPath = null)
 28    {
 32129        resolvedPath = null;
 33630        if (fields is null || path.Length == 0) return null;
 31
 30632        var pathSegments = new List<string>();
 30633        var currentField = WalkPath(fields, path, pathSegments);
 33634        if (currentField is null) return null;
 35
 27636        var joinedPath = string.Join(".", pathSegments);
 27637        resolvedPath = prependPath is not null ? $"{prependPath}.{joinedPath}" : joinedPath;
 27638        return currentField;
 39    }
 40
 41    private static FieldDefinition? WalkPath(IReadOnlyDictionary<string, FieldDefinition> fields, ReadOnlySpan<char> pat
 42    {
 30643        var currentFields = fields;
 30644        FieldDefinition? currentField = null;
 45
 65146        while (path.Length > 0)
 47        {
 65148            var dotIndex = path.IndexOf('.');
 65149            var segment = dotIndex >= 0 ? path[..dotIndex] : path;
 50
 65151            var match = PreserveExtensions.FindFieldByNameOrAlias(currentFields, segment);
 67852            if (!match.HasValue) return null;
 53
 62454            currentField = match.Value.Value;
 62455            pathSegments.Add(match.Value.Key);
 56
 62457            if (dotIndex < 0) break;
 35158            if (currentField._children is null) return null;
 59
 34560            currentFields = currentField.Fields;
 34561            path = path[(dotIndex + 1)..];
 62        }
 63
 27664        return currentField;
 65    }
 66
 67    /// <summary>
 68    /// Recursively searches for a field by name through all descendant nodes.
 69    /// Returns all matching paths.
 70    /// </summary>
 71    internal static List<string> FindFieldRecursively(
 72        IReadOnlyDictionary<string, FieldDefinition> fields,
 73        string fieldName,
 74        string basePath)
 75    {
 1876        var results = new List<string>();
 1877        FindFieldRecursivelyCore(fields, fieldName, basePath, results);
 1878        return results;
 79    }
 80
 81    private static void FindFieldRecursivelyCore(
 82        IReadOnlyDictionary<string, FieldDefinition> fields,
 83        string fieldName,
 84        string basePath,
 85        List<string> results)
 86    {
 22887        foreach (var (key, fieldDef) in fields)
 88        {
 7889            var currentPath = string.IsNullOrEmpty(basePath) ? key : $"{basePath}.{key}";
 90
 7891            if (NameOrAliasMatches(fieldDef, fieldName))
 92            {
 1893                results.Add(currentPath);
 94            }
 95
 7896            if (fieldDef.HasFields)
 97            {
 1898                FindFieldRecursivelyCore(fieldDef._children!, fieldName, currentPath, results);
 99            }
 100        }
 36101    }
 102
 103    private static bool NameOrAliasMatches(FieldDefinition field, string name)
 104    {
 96105        if (string.Equals(field.Name, name, StringComparison.OrdinalIgnoreCase)) return true;
 60106        if (string.IsNullOrEmpty(field.Alias)) return false;
 60107        return string.Equals(field.Alias, name, StringComparison.OrdinalIgnoreCase);
 108    }
 109}