< Summary

Information
Class: NGql.Core.Abstractions.QueryDefinition
Assembly: NGql.Core
File(s): /home/runner/work/NGql/NGql/src/Core/Abstractions/QueryDefinition.cs
Line coverage
100%
Covered lines: 51
Uncovered lines: 0
Coverable lines: 51
Total lines: 225
Line coverage: 100%
Branch coverage
100%
Covered branches: 20
Total branches: 20
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_MergeIndex()100%22100%
get_Name()100%11100%
get_Description()100%11100%
get_Fields()100%22100%
get_FieldsInternal()100%22100%
get_Variables()100%22100%
set_Variables(...)100%11100%
get_Metadata()100%22100%
get_MetadataInternal()100%22100%
get_NamedFragments()100%22100%
.cctor()100%11100%
GetOrAddNamedFragment(...)100%66100%
get_MergingStrategy()100%11100%
get_OperationType()100%11100%
ToString()100%11100%
AppendTo(...)100%11100%
WriteTo(...)100%11100%
WriteUtf8(...)100%11100%
op_Implicit(...)100%11100%

File(s)

/home/runner/work/NGql/NGql/src/Core/Abstractions/QueryDefinition.cs

#LineLine coverage
 1using System.Buffers;
 2using System.Text;
 3using System.Text.Json.Serialization;
 4using NGql.Core.Builders;
 5using NGql.Core.Features;
 6
 7namespace NGql.Core.Abstractions;
 8
 9/// <summary>
 10///     Represents a query definition.
 11/// </summary>
 1339212public sealed record QueryDefinition(string Name, string Description = "")
 13{
 14    internal Dictionary<string, FieldDefinition>? _fields;
 15    internal SortedSet<Variable>? _variables;
 16    internal Dictionary<string, object?>? _metadata;
 17    internal Dictionary<string, NamedFragmentDefinition>? _namedFragments;
 18
 19    /// <summary>
 20    ///     Merge-candidate index over the root field dictionary, used by
 21    ///     <see cref="Features.QueryMerger"/> to avoid an O(N) scan per <see cref="MergingStrategy.MergeByFieldPath"/>
 22    ///     <c>Include()</c> call. Lazily created; excluded from record equality/deep-clone semantics —
 23    ///     it is a derived cache over <see cref="_fields"/>, not query data.
 24    /// </summary>
 25    internal FieldMergeIndex? _mergeIndex;
 26
 27    /// <summary>
 28    ///     Lazily creates (or returns) the merge-candidate index for this definition's root field
 29    ///     dictionary. Never shared across <see cref="QueryDefinition"/> instances.
 30    /// </summary>
 364831    internal FieldMergeIndex MergeIndex => _mergeIndex ??= new FieldMergeIndex();
 32
 33    /// <summary>
 34    /// The name of the query.
 35    /// </summary>
 36    [JsonPropertyName("name")]
 11807137    public string Name { get; init; } = Name;
 38
 39    /// <summary>
 40    /// The description of the query.
 41    /// </summary>
 42    [JsonPropertyName("description")]
 1339243    public string Description { get; init; } = Description;
 44
 45    /// <summary>
 46    ///     The collection of fields related to <see cref="QueryDefinition"/>.
 47    ///     Exposed read-only: mutating the field set from outside the assembly would desync
 48    ///     the builder's path index and query map. Fields are added through the
 49    ///     <see cref="QueryBuilder"/> API; internal code mutates via <see cref="FieldsInternal"/>.
 50    /// </summary>
 51    [JsonPropertyName("fields")]
 52    public IReadOnlyDictionary<string, FieldDefinition> Fields
 7204853        => _fields ??= new(StringComparer.OrdinalIgnoreCase);
 54
 55    /// <summary>
 56    ///     Mutable view of <see cref="Fields"/> for internal builder and merge code. Lazily
 57    ///     initialized with the same case-insensitive comparer as the public getter.
 58    /// </summary>
 59    internal Dictionary<string, FieldDefinition> FieldsInternal
 13994760        => _fields ??= new(StringComparer.OrdinalIgnoreCase);
 61
 62    /// <summary>
 63    ///     The collection of variables related to fields or arguments.
 64    /// </summary>
 65    [JsonIgnore]
 66    public SortedSet<Variable> Variables
 67    {
 1113068        get => _variables ??= new();
 1869        internal set => _variables = value;
 70    }
 71
 72    /// <summary>
 73    /// Metadata associated with the query definition.
 74    /// This can include additional information such as descriptions, tags, or any other relevant data.
 75    ///
 76    /// Not used during query text generation but can be useful for documentation or introspection purposes.
 77    ///
 78    /// Exposed read-only: replacing or mutating the bag from outside the assembly is not
 79    /// supported. Metadata is attached through <see cref="QueryBuilder.WithMetadata"/>;
 80    /// internal code mutates via <see cref="MetadataInternal"/>.
 81    /// </summary>
 82    [JsonPropertyName("metadata")]
 19583    public IReadOnlyDictionary<string, object?> Metadata => _metadata ??= [];
 84
 85    /// <summary>
 86    ///     Mutable view of <see cref="Metadata"/> for internal builder and preservation code.
 87    /// </summary>
 3688    internal Dictionary<string, object?> MetadataInternal => _metadata ??= [];
 89
 90    /// <summary>
 91    ///     Named fragments declared at this operation's top level, keyed by fragment name
 92    ///     (case-sensitive). Each fragment renders as <c>fragment Name on TypeName { … }</c>
 93    ///     after the operation block, sorted alphabetically by name.
 94    /// </summary>
 95    /// <remarks>
 96    ///     Fragments are referenced from a field's selection set via
 97    ///     <see cref="FieldDefinition.SpreadFragments"/>. NGql does not validate that every
 98    ///     spread points at a declared fragment — undeclared spreads render as <c>...Name</c>
 99    ///     and the server rejects them with a clear error. This keeps NGql schemaless.
 100    /// </remarks>
 101    [JsonPropertyName("namedFragments")]
 102    public IReadOnlyDictionary<string, NamedFragmentDefinition> NamedFragments
 36103        => (IReadOnlyDictionary<string, NamedFragmentDefinition>?)_namedFragments ?? EmptyNamedFragments;
 104
 3105    private static readonly IReadOnlyDictionary<string, NamedFragmentDefinition> EmptyNamedFragments
 3106        = new Dictionary<string, NamedFragmentDefinition>();
 107
 108    /// <summary>
 109    /// Returns the existing named fragment for <paramref name="name"/>, or appends a new one
 110    /// declared on <paramref name="onType"/>. Used by the builder to back
 111    /// <c>QueryBuilder.AddFragment</c>. Throws <see cref="InvalidOperationException"/> if the
 112    /// fragment exists with a different <c>OnType</c> — silent override is the worst failure
 113    /// mode for a refactor that mistakenly reuses a fragment name.
 114    /// </summary>
 115    internal NamedFragmentDefinition GetOrAddNamedFragment(string name, string onType)
 116    {
 231117        _namedFragments ??= new Dictionary<string, NamedFragmentDefinition>(StringComparer.Ordinal);
 231118        if (_namedFragments.TryGetValue(name, out var fragment))
 119        {
 27120            if (!string.Equals(fragment.OnType, onType, StringComparison.Ordinal))
 121            {
 9122                throw new InvalidOperationException(
 9123                    $"Named fragment '{name}' is already declared on type '{fragment.OnType}' — cannot redeclare on '{on
 124            }
 18125            return fragment;
 126        }
 127
 204128        fragment = new NamedFragmentDefinition(name, onType);
 204129        _namedFragments[name] = fragment;
 204130        return fragment;
 131    }
 132
 133    /// <summary>
 134    /// Merging strategy for this query definition.
 135    /// </summary>
 136    [JsonPropertyName("mergingStrategy")]
 8670137    public MergingStrategy MergingStrategy { get; set; } = MergingStrategy.MergeByDefault;
 138
 139    /// <summary>
 140    /// The kind of root operation rendered by this definition (query, mutation, or subscription).
 141    /// Selected by the factory on <see cref="QueryBuilder"/>; consumers do not set this directly.
 142    /// </summary>
 143    [JsonIgnore]
 26229144    internal OperationType OperationType { get; set; } = OperationType.Query;
 145
 146    /// <inheritdoc cref="QueryBlock.ToString()"/>
 147    public override string ToString()
 148    {
 24789149        var builder = QueryTextBuilder.GetFromPool();
 150        try
 151        {
 24789152            return builder.Build(this);
 153        }
 154        finally
 155        {
 24789156            QueryTextBuilder.ReturnToPool(builder);
 24789157        }
 24765158    }
 159
 160    /// <summary>
 161    /// Renders this definition's GraphQL and appends it to <paramref name="builder"/> without
 162    /// materializing the intermediate string that <see cref="ToString()"/> allocates. The
 163    /// appended text is byte-for-byte identical to <see cref="ToString()"/>.
 164    /// </summary>
 165    /// <param name="builder">The target <see cref="StringBuilder"/> to append to.</param>
 166    /// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> is null.</exception>
 167    public void AppendTo(StringBuilder builder)
 168    {
 48169        ArgumentNullException.ThrowIfNull(builder);
 48170        var textBuilder = QueryTextBuilder.GetFromPool();
 171        try
 172        {
 48173            textBuilder.BuildInto(this, builder);
 48174        }
 175        finally
 176        {
 48177            QueryTextBuilder.ReturnToPool(textBuilder);
 48178        }
 48179    }
 180
 181    /// <summary>
 182    /// Renders this definition's GraphQL and writes it to <paramref name="writer"/> without
 183    /// materializing the intermediate string that <see cref="ToString()"/> allocates. The
 184    /// written text is byte-for-byte identical to <see cref="ToString()"/>.
 185    /// </summary>
 186    /// <param name="writer">The target <see cref="TextWriter"/> to write to.</param>
 187    /// <exception cref="ArgumentNullException">Thrown when <paramref name="writer"/> is null.</exception>
 188    public void WriteTo(TextWriter writer)
 189    {
 1221190        ArgumentNullException.ThrowIfNull(writer);
 1221191        var textBuilder = QueryTextBuilder.GetFromPool();
 192        try
 193        {
 1221194            textBuilder.BuildInto(this, writer);
 1221195        }
 196        finally
 197        {
 1221198            QueryTextBuilder.ReturnToPool(textBuilder);
 1221199        }
 1221200    }
 201
 202    /// <summary>
 203    /// Renders this definition's GraphQL and transcodes it as UTF-8 directly into
 204    /// <paramref name="bufferWriter"/>, with no intermediate <see cref="string"/> or <c>byte[]</c>
 205    /// allocation. The written bytes are identical to
 206    /// <c>System.Text.Encoding.UTF8.GetBytes(</c><see cref="ToString()"/><c>)</c>.
 207    /// </summary>
 208    /// <param name="bufferWriter">The target <see cref="IBufferWriter{Byte}"/> to write UTF-8 bytes to.</param>
 209    /// <exception cref="ArgumentNullException">Thrown when <paramref name="bufferWriter"/> is null.</exception>
 210    public void WriteUtf8(IBufferWriter<byte> bufferWriter)
 211    {
 90212        ArgumentNullException.ThrowIfNull(bufferWriter);
 90213        var textBuilder = QueryTextBuilder.GetFromPool();
 214        try
 215        {
 90216            textBuilder.BuildInto(this, bufferWriter);
 90217        }
 218        finally
 219        {
 90220            QueryTextBuilder.ReturnToPool(textBuilder);
 90221        }
 90222    }
 223
 3224    public static implicit operator string(QueryDefinition query) => query.ToString();
 225}