< Summary

Information
Class: NGql.Core.Extensions.QueryBlockObjectExtensions
Assembly: NGql.Core
File(s): /home/runner/work/NGql/NGql/src/Core/Extensions/QueryBlockObjectExtensions.cs
Line coverage
100%
Covered lines: 85
Uncovered lines: 0
Coverable lines: 85
Total lines: 201
Line coverage: 100%
Branch coverage
100%
Covered branches: 46
Total branches: 46
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GetArguments(...)100%22100%
CopyArguments(...)100%66100%
AddMissingRootVariables(...)100%44100%
ContainsVariableNamed(...)100%22100%
IncludeAtPath(...)100%88100%
Include(...)100%22100%
Include(...)100%11100%
HandleProperties(...)100%22100%
HandleProperty(...)100%88100%
AddSimpleProperty(...)100%44100%
HandleDictionary(...)100%44100%
.cctor()100%11100%
IsSimpleType(...)100%44100%

File(s)

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

#LineLine coverage
 1using System.Collections;
 2using System.Reflection;
 3using NGql.Core.Abstractions;
 4
 5namespace NGql.Core.Extensions;
 6
 7internal static class QueryBlockObjectExtensions
 8{
 9    internal static SortedDictionary<string, object> GetArguments(this QueryBlock queryBlock, bool isRootElement)
 10    {
 43211        var arguments = new SortedDictionary<string, object>(StringComparer.Ordinal);
 43212        CopyArguments(queryBlock, isRootElement, arguments);
 13
 43214        if (isRootElement)
 15        {
 20716            AddMissingRootVariables(queryBlock, arguments);
 17        }
 43218        return arguments;
 19    }
 20
 21    private static void CopyArguments(QueryBlock queryBlock, bool isRootElement, SortedDictionary<string, object> argume
 22    {
 124823        foreach (var kvp in queryBlock.Arguments)
 24        {
 19225            var key = kvp.Value is Variable variable && isRootElement ? variable.Name : kvp.Key;
 19226            arguments[key] = kvp.Value;
 27        }
 43228    }
 29
 30    private static void AddMissingRootVariables(QueryBlock queryBlock, SortedDictionary<string, object> arguments)
 31    {
 65732        foreach (var variable in queryBlock.Variables.Where(v => !ContainsVariableNamed(arguments, v.Name)))
 33        {
 7534            arguments[variable.Name] = variable;
 35        }
 20736    }
 37
 38    private static bool ContainsVariableNamed(SortedDictionary<string, object> arguments, string name)
 15639        => arguments.Values.Any(v => v is Variable existing && existing.Name == name);
 40
 41    /// <summary>
 42    /// Adds the given type properties into <see cref="QueryBlock.FieldsList"/> part of the query.
 43    /// </summary>
 44    /// <param name="block">The query block</param>
 45    /// <param name="path">The path to use</param>
 46    /// <param name="name">The name to use</param>
 47    /// <param name="alias">The alias to use</param>
 48    /// <typeparam name="T">The type to include</typeparam>
 49    public static void IncludeAtPath<T>(this QueryBlock block, string path, string name, string? alias = null)
 50    {
 51        // Use Span to avoid string allocations during path parsing
 1852        var pathSpan = path.AsSpan();
 1853        var currentBlock = block;
 54
 5155        while (!pathSpan.IsEmpty)
 56        {
 3357            var dotIndex = pathSpan.IndexOf('.');
 58            ReadOnlySpan<char> currentSegment;
 59
 3360            if (dotIndex == -1)
 61            {
 62                // Last segment
 1263                currentSegment = pathSpan;
 1264                pathSpan = ReadOnlySpan<char>.Empty;
 65            }
 66            else
 67            {
 68                // Extract current segment and advance
 2169                currentSegment = pathSpan[..dotIndex];
 2170                pathSpan = pathSpan[(dotIndex + 1)..];
 71            }
 72
 73            // Skip empty segments
 3374            if (currentSegment.IsEmpty || currentSegment.IsWhiteSpace())
 75            {
 76                continue;
 77            }
 78
 79            // Convert to string only when creating the QueryBlock
 2780            var segmentString = currentSegment.ToString();
 2781            var subQuery = new QueryBlock(segmentString);
 2782            currentBlock.AddField(subQuery);
 2783            currentBlock = subQuery;
 84        }
 85
 1886        currentBlock.Include<T>(name, alias);
 1887    }
 88
 89    /// <summary>
 90    /// Adds the given type properties into <see cref="QueryBlock.FieldsList"/> part of the query.
 91    /// </summary>
 92    /// <param name="block">The query block</param>
 93    /// <param name="name">The name to use</param>
 94    /// <param name="alias"></param>
 95    /// <returns>Query</returns>
 96    public static void Include<T>(this QueryBlock block, string name, string? alias = null)
 97    {
 2498        var properties = typeof(T).GetProperties()
 7299            .OrderBy(p => p.Name, StringComparer.OrdinalIgnoreCase)
 24100            .ToArray();
 101
 24102        var subQuery = new QueryBlock(name, alias: alias);
 103
 24104        HandleProperties(subQuery, null, properties);
 105
 24106        block.AddField(subQuery);
 24107    }
 108
 109    /// <summary>
 110    /// Adds the given object properties into <see cref="QueryBlock.FieldsList"/> part of the query.
 111    /// </summary>
 112    /// <param name="block">The query block</param>
 113    /// <param name="obj">A value</param>
 114    /// <returns>Query</returns>
 115    public static void Include(this QueryBlock block, object obj)
 116    {
 15117        var type = obj.GetType();
 15118        var properties = type.GetProperties()
 33119            .OrderBy(p => p.Name, StringComparer.OrdinalIgnoreCase)
 15120            .ToArray();
 121
 15122        HandleProperties(block, obj, properties);
 15123    }
 124
 125    private static void HandleProperties(QueryBlock block, object? obj, PropertyInfo[] properties)
 126    {
 288127        foreach (var property in properties)
 128        {
 105129            HandleProperty(block, obj, property);
 130        }
 39131    }
 132
 133    private static void HandleProperty(QueryBlock block, object? obj, PropertyInfo property)
 134    {
 105135        var value = obj is null ? null : property.GetValue(obj);
 105136        var alias = property.GetAlias();
 137
 105138        if (value is IDictionary dict)
 139        {
 3140            HandleDictionary(block, property.Name, alias, dict);
 3141            return;
 142        }
 102143        if (value is not null && !IsSimpleType(value.GetType()))
 144        {
 6145            var subQuery = new QueryBlock(property.Name, alias: alias);
 6146            subQuery.Include(value);
 6147            block.AddField(subQuery);
 6148            return;
 149        }
 96150        AddSimpleProperty(block, property.Name, alias);
 96151    }
 152
 153    private static void AddSimpleProperty(QueryBlock block, string propertyName, string? alias)
 154    {
 96155        if (alias == propertyName)
 156        {
 27157            block.AddField(propertyName);
 27158            return;
 159        }
 69160        block.AddField(alias is null
 69161            ? new QueryBlock(propertyName) { IsEmpty = true }
 69162            : new QueryBlock(alias, alias: propertyName) { IsEmpty = true });
 69163    }
 164
 165    private static void HandleDictionary(QueryBlock block, string name, string? alias, IDictionary dict)
 166    {
 6167        var subQuery = new QueryBlock(name, alias: alias);
 6168        var sortedKeys = dict.Keys.Cast<object>()
 18169            .OrderBy(k => k.ToString(), StringComparer.OrdinalIgnoreCase);
 170
 36171        foreach (var key in sortedKeys)
 172        {
 12173            if (dict[key] is IDictionary nestedDict)
 174            {
 3175                HandleDictionary(subQuery, key.ToString()!, null, nestedDict);
 176            }
 177            else
 178            {
 9179                subQuery.AddField(key.ToString()!);
 180            }
 181        }
 182
 6183        block.AddField(subQuery);
 6184    }
 185
 3186    private static readonly HashSet<Type> SimpleTypes =
 3187    [
 3188        typeof(string),
 3189        typeof(decimal),
 3190        typeof(DateTime),
 3191        typeof(DateTimeOffset),
 3192        typeof(TimeSpan),
 3193        typeof(Guid)
 3194    ];
 195
 196    private static bool IsSimpleType(Type type)
 27197        => type.IsPrimitive ||
 27198            SimpleTypes.Contains(type) ||
 27199            Convert.GetTypeCode(type) != TypeCode.Object;
 200}
 201