< Summary

Information
Class: NGql.Core.Builders.FieldBuilder
Assembly: NGql.Core
File(s): /home/runner/work/NGql/NGql/src/Core/Builders/FieldBuilder.cs
Line coverage
100%
Covered lines: 109
Uncovered lines: 0
Coverable lines: 109
Total lines: 633
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
.ctor(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddField(...)100%11100%
AddFieldCore(...)100%1010100%
Create(...)100%44100%
Create(...)100%11100%
Build()100%11100%
RecursiveCreateField(...)100%66100%
RecursiveCreateField(...)100%66100%
Include(...)100%11100%
ValidateFieldNameSegments(...)100%1010100%
WithAlias(...)100%11100%
WithType(...)100%11100%
WithMetadata(...)100%11100%
Where(...)100%88100%
OnType(...)100%22100%

File(s)

/home/runner/work/NGql/NGql/src/Core/Builders/FieldBuilder.cs

#LineLine coverage
 1using System.Runtime.CompilerServices;
 2using NGql.Core.Abstractions;
 3using NGql.Core.Extensions;
 4
 5namespace NGql.Core.Builders;
 6
 7/// <summary>
 8/// Fluent helper passed to the <c>fieldBuilder</c> action overloads of
 9/// <see cref="QueryBuilder.AddField(string, Action{FieldBuilder})"/>. Use it to add
 10/// nested fields, configure a parent field's type/arguments/metadata, or compose
 11/// sub-trees without leaving the outer chain.
 12/// </summary>
 13public sealed class FieldBuilder
 14{
 15    private FieldDefinition _fieldDefinition;
 16
 2328917    private FieldBuilder(FieldDefinition fieldDefinition)
 18    {
 2328919        _fieldDefinition = fieldDefinition;
 2328920    }
 21
 22    /// <summary>
 23    /// Adds a field definition to the builder.
 24    /// </summary>
 25    /// <param name="fieldDefinition">The field definition to add.</param>
 26    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 27    /// <exception cref="ArgumentNullException">Thrown when fieldDefinition is null.</exception>
 28    public FieldBuilder AddField(FieldDefinition fieldDefinition)
 29    {
 76230        ArgumentNullException.ThrowIfNull(fieldDefinition);
 31        // Dotted names are valid here — they are expanded into nested fields by FieldFactory,
 32        // matching the string-overload behavior. Validate each segment individually.
 76233        ValidateFieldNameSegments(fieldDefinition.Name.AsSpan());
 75934        var arguments = fieldDefinition._arguments;
 35
 36        // FieldDefinition._type is always set non-null by every constructor path.
 75937        FieldFactory.GetOrAddField(_fieldDefinition, fieldDefinition.Name, fieldDefinition._type!, arguments, _fieldDefi
 75938        return this;
 39    }
 40
 41    /// <summary>
 42    /// Adds a field with nested subfields, optional arguments, and metadata to the builder.
 43    /// This creates an object-type field that contains the specified subfields.
 44    /// </summary>
 45    /// <param name="fieldName">The name of the parent field to add. Supports dotted notation for nested fields.</param>
 46    /// <param name="subFields">Array of subfield names to add under this field. Each subfield will have default String 
 47    /// <param name="arguments">Optional GraphQL arguments for the parent field (e.g., filtering, pagination parameters)
 48    /// <param name="metadata">Optional metadata dictionary to associate with the parent field.</param>
 49    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 50    /// <exception cref="ArgumentNullException">Thrown when fieldName or subFields is null.</exception>
 51    /// <exception cref="ArgumentException">Thrown when fieldName is empty or subFields array is empty.</exception>
 52    public FieldBuilder AddField(string fieldName, string[] subFields, Dictionary<string, object?>? arguments = null, Di
 11153        => AddFieldCore(fieldName, Constants.ObjectFieldType, arguments, subFields, metadata);
 54
 55    /// <summary>
 56    /// Adds a field with arguments, then nested subfields and optional metadata. This is the args-first
 57    /// counterpart to <see cref="AddField(string, string[], Dictionary{string, object?}?, Dictionary{string, object?}?)
 58    /// and matches the parameter order used by <see cref="QueryBuilder.AddField(string, Dictionary{string, object?}, st
 59    /// </summary>
 60    /// <param name="fieldName">The name of the parent field to add.</param>
 61    /// <param name="arguments">GraphQL arguments for the parent field.</param>
 62    /// <param name="subFields">Array of subfield names to add under this field.</param>
 63    /// <param name="metadata">Optional metadata dictionary to associate with the parent field.</param>
 64    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 65    public FieldBuilder AddField(string fieldName, Dictionary<string, object?>? arguments, string[] subFields, Dictionar
 366        => AddFieldCore(fieldName, Constants.ObjectFieldType, arguments, subFields, metadata);
 67
 68    /// <summary>
 69    /// Adds a field with a specific type, GraphQL arguments, and optional metadata to the builder.
 70    /// </summary>
 71    /// <param name="fieldName">The name of the field to add. Supports dotted notation for nested fields.</param>
 72    /// <param name="type">The GraphQL type of the field (e.g., "String!", "[String]", custom types).</param>
 73    /// <param name="arguments">GraphQL arguments for the field such as filters, pagination, or custom parameters.</para
 74    /// <param name="metadata">Optional metadata dictionary to associate with the field.</param>
 75    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 76    /// <exception cref="ArgumentNullException">Thrown when fieldName or type is null.</exception>
 77    /// <example>
 78    /// <code>
 79    /// builder.AddField("users", "User", new Dictionary&lt;string, object?&gt; { ["first"] = 10 });
 80    /// </code>
 81    /// </example>
 82    public FieldBuilder AddField(string fieldName, string type, Dictionary<string, object?>? arguments, Dictionary<strin
 12983        => AddFieldCore(fieldName, type, arguments, metadata: metadata);
 84
 85    /// <summary>
 86    /// Adds a field with a specific type, nested subfields, and optional metadata to the builder.
 87    /// </summary>
 88    /// <param name="fieldName">The name of the parent field to add. Supports dotted notation for nested fields.</param>
 89    /// <param name="type">The GraphQL type of the parent field (typically an object type or array type).</param>
 90    /// <param name="subFields">Array of subfield names to add under this field. Each subfield will have default String 
 91    /// <param name="metadata">Optional metadata dictionary to associate with the parent field.</param>
 92    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 93    /// <exception cref="ArgumentNullException">Thrown when fieldName, type, or subFields is null.</exception>
 94    /// <example>
 95    /// <code>
 96    /// builder.AddField("user", "User", new[] { "id", "name", "email" });
 97    /// </code>
 98    /// </example>
 99    public FieldBuilder AddField(string fieldName, string type, string[] subFields, Dictionary<string, object?>? metadat
 84100        => AddFieldCore(fieldName, type, subFields: subFields, metadata: metadata);
 101
 102    /// <summary>
 103    /// Adds a field with GraphQL arguments to the builder. The field will have the default String type.
 104    /// </summary>
 105    /// <param name="fieldName">The name of the field to add. Supports dotted notation for nested fields.</param>
 106    /// <param name="arguments">GraphQL arguments for the field such as filters, pagination, or custom parameters.</para
 107    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 108    /// <exception cref="ArgumentNullException">Thrown when fieldName is null.</exception>
 109    /// <example>
 110    /// <code>
 111    /// builder.AddField("searchResults", new SortedDictionary&lt;string, object?&gt; { ["query"] = "GraphQL" });
 112    /// </code>
 113    /// </example>
 114    public FieldBuilder AddField(string fieldName, Dictionary<string, object?>? arguments)
 21115        => AddFieldCore(fieldName, arguments: arguments);
 116
 117    /// <summary>
 118    /// Adds a field with GraphQL arguments and metadata to the builder. The field will have the default String type.
 119    /// </summary>
 120    /// <param name="fieldName">The name of the field to add. Supports dotted notation for nested fields.</param>
 121    /// <param name="arguments">GraphQL arguments for the field such as filters, pagination, or custom parameters.</para
 122    /// <param name="metadata">Optional metadata dictionary to associate with the field for custom processing.</param>
 123    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 124    /// <exception cref="ArgumentNullException">Thrown when fieldName is null.</exception>
 125    public FieldBuilder AddField(string fieldName, Dictionary<string, object?>? arguments, Dictionary<string, object?>? 
 6126        => AddFieldCore(fieldName, arguments: arguments, metadata: metadata);
 127
 128    /// <summary>
 129    /// Adds a field with a specific type, nested subfields, GraphQL arguments, and metadata to the builder.
 130    /// This is the most comprehensive overload that supports all field configuration options.
 131    /// </summary>
 132    /// <param name="fieldName">The name of the parent field to add. Supports dotted notation for nested fields.</param>
 133    /// <param name="type">The GraphQL type of the parent field (typically an object type or array type).</param>
 134    /// <param name="subFields">Array of subfield names to add under this field. Each subfield will have default String 
 135    /// <param name="arguments">GraphQL arguments for the parent field such as filters, pagination, or custom parameters
 136    /// <param name="metadata">Optional metadata dictionary to associate with the parent field for custom processing.</p
 137    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 138    /// <exception cref="ArgumentNullException">Thrown when fieldName, type, or subFields is null.</exception>
 139    public FieldBuilder AddField(string fieldName, string type, string[] subFields, Dictionary<string, object?>? argumen
 6140        => AddFieldCore(fieldName, type, arguments, subFields, metadata);
 141
 142    /// <summary>
 143    /// Adds a field with a nested builder action for configuring subfields dynamically.
 144    /// The field will have the default String type and allows fluent configuration of nested fields.
 145    /// </summary>
 146    /// <param name="fieldName">The name of the field to add. Supports dotted notation for nested fields.</param>
 147    /// <param name="action">A delegate that receives a FieldBuilder instance for configuring nested fields within this 
 148    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 149    /// <exception cref="ArgumentNullException">Thrown when fieldName or action is null.</exception>
 150    /// <example>
 151    /// <code>
 152    /// builder.AddField("user", user => {
 153    ///     user.AddField("id");
 154    ///     user.AddField("profile", profile => {
 155    ///         profile.AddField("name");
 156    ///         profile.AddField("email");
 157    ///     });
 158    /// });
 159    /// </code>
 160    /// </example>
 161    public FieldBuilder AddField(string fieldName, Action<FieldBuilder> action)
 132162        => AddFieldCore(fieldName, action: action);
 163
 164    /// <summary>
 165    /// Adds a field with GraphQL arguments and a nested builder action for configuring subfields dynamically.
 166    /// The field will have the default String type.
 167    /// </summary>
 168    /// <remarks>
 169    /// <b>Behavior change in 2.1:</b> the dictionary at this position is now interpreted as
 170    /// <c>arguments</c>, matching the conventions of <see cref="QueryBuilder.AddField(string, Dictionary{string, object
 171    /// and the rest of the <see cref="FieldBuilder"/> args-first overloads. In NGql 2.0 the same
 172    /// signature was interpreted as <c>metadata</c>; callers that relied on the old behavior must
 173    /// switch to the four-arg form with named arguments:
 174    /// <c>AddField(field, arguments: null, metadata: dict, action)</c>.
 175    /// </remarks>
 176    /// <param name="fieldName">The name of the field to add. Supports dotted notation for nested fields.</param>
 177    /// <param name="arguments">GraphQL arguments for the field.</param>
 178    /// <param name="action">A delegate that receives a FieldBuilder instance for configuring nested fields within this 
 179    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 180    /// <exception cref="ArgumentNullException">Thrown when fieldName or action is null.</exception>
 181    public FieldBuilder AddField(string fieldName, Dictionary<string, object?>? arguments, Action<FieldBuilder> action)
 6182        => AddFieldCore(fieldName, arguments: arguments, action: action);
 183
 184    /// <summary>
 185    /// Adds a field with a specific type and a nested builder action for configuring subfields dynamically.
 186    /// </summary>
 187    /// <param name="fieldName">The name of the field to add. Supports dotted notation for nested fields.</param>
 188    /// <param name="type">The GraphQL type of the field (typically an object type when using nested actions).</param>
 189    /// <param name="action">A delegate that receives a FieldBuilder instance for configuring nested fields within this 
 190    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 191    /// <exception cref="ArgumentNullException">Thrown when fieldName, type, or action is null.</exception>
 192    /// <example>
 193    /// <code>
 194    /// builder.AddField("posts", "[Post]", posts => {
 195    ///     posts.AddField("id", "ID!");
 196    ///     posts.AddField("title");
 197    ///     posts.AddField("author", "User", author => {
 198    ///         author.AddField("name");
 199    ///     });
 200    /// });
 201    /// </code>
 202    /// </example>
 203    public FieldBuilder AddField(string fieldName, string type, Action<FieldBuilder> action)
 15204        => AddFieldCore(fieldName, type, action: action);
 205
 206    /// <summary>
 207    /// Adds a field with a specific type, metadata, and a nested builder action for configuring subfields dynamically.
 208    /// </summary>
 209    /// <param name="fieldName">The name of the field to add. Supports dotted notation for nested fields.</param>
 210    /// <param name="type">The GraphQL type of the field (typically an object type when using nested actions).</param>
 211    /// <param name="metadata">Optional metadata dictionary to associate with the field for custom processing.</param>
 212    /// <param name="action">A delegate that receives a FieldBuilder instance for configuring nested fields within this 
 213    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 214    /// <exception cref="ArgumentNullException">Thrown when fieldName, type, or action is null.</exception>
 215    public FieldBuilder AddField(string fieldName, string type, Dictionary<string, object?>? metadata, Action<FieldBuild
 6216        => AddFieldCore(fieldName, type, metadata: metadata, action: action);
 217
 218    /// <summary>
 219    /// Adds a field with GraphQL arguments, metadata, and a nested builder action for configuring subfields dynamically
 220    /// The field will have the default String type.
 221    /// </summary>
 222    /// <param name="fieldName">The name of the field to add. Supports dotted notation for nested fields.</param>
 223    /// <param name="arguments">GraphQL arguments for the field such as filters, pagination, or custom parameters.</para
 224    /// <param name="metadata">Optional metadata dictionary to associate with the field for custom processing.</param>
 225    /// <param name="action">A delegate that receives a FieldBuilder instance for configuring nested fields within this 
 226    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 227    /// <exception cref="ArgumentNullException">Thrown when fieldName or action is null.</exception>
 228    public FieldBuilder AddField(string fieldName, Dictionary<string, object?>? arguments, Dictionary<string, object?>? 
 9229        => AddFieldCore(fieldName, arguments: arguments, metadata: metadata, action: action);
 230
 231    /// <summary>
 232    /// Adds a field with a specific type, GraphQL arguments, metadata, and a nested builder action for configuring subf
 233    /// This is the most comprehensive action-based overload that supports all field configuration options.
 234    /// </summary>
 235    /// <param name="fieldName">The name of the field to add. Supports dotted notation for nested fields.</param>
 236    /// <param name="type">The GraphQL type of the field (typically an object type when using nested actions).</param>
 237    /// <param name="arguments">GraphQL arguments for the field such as filters, pagination, or custom parameters.</para
 238    /// <param name="metadata">Optional metadata dictionary to associate with the field for custom processing.</param>
 239    /// <param name="action">A delegate that receives a FieldBuilder instance for configuring nested fields within this 
 240    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 241    /// <exception cref="ArgumentNullException">Thrown when fieldName, type, or action is null.</exception>
 242    public FieldBuilder AddField(string fieldName, string type, Dictionary<string, object?>? arguments, Dictionary<strin
 3243        => AddFieldCore(fieldName, type, arguments, metadata: metadata, action: action);
 244
 245    /// <summary>
 246    /// Adds a field with predefined subfields and a nested builder action for additional dynamic configuration.
 247    /// The field will have an object type and includes both the specified subfields and any fields configured via the a
 248    /// </summary>
 249    /// <param name="fieldName">The name of the parent field to add. Supports dotted notation for nested fields.</param>
 250    /// <param name="subFields">Array of subfield names to add under this field. Each subfield will have a default Strin
 251    /// <param name="action">A delegate that receives a FieldBuilder instance for configuring additional nested fields w
 252    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 253    /// <exception cref="ArgumentNullException">Thrown when fieldName, subFields, or action is null.</exception>
 254    public FieldBuilder AddField(string fieldName, string[] subFields, Action<FieldBuilder> action)
 6255        => AddFieldCore(fieldName, Constants.ObjectFieldType, subFields: subFields, action: action);
 256
 257    /// <summary>
 258    /// Adds a field with predefined subfields, metadata, and a nested builder action for additional dynamic configurati
 259    /// The field will have an object type.
 260    /// </summary>
 261    /// <param name="fieldName">The name of the parent field to add. Supports dotted notation for nested fields.</param>
 262    /// <param name="subFields">Array of subfield names to add under this field. Each subfield will have a default Strin
 263    /// <param name="metadata">Optional metadata dictionary to associate with the parent field for custom processing.</p
 264    /// <param name="action">A delegate that receives a FieldBuilder instance for configuring additional nested fields w
 265    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 266    /// <exception cref="ArgumentNullException">Thrown when fieldName, subFields, or action is null.</exception>
 267    public FieldBuilder AddField(string fieldName, string[] subFields, Dictionary<string, object?>? metadata, Action<Fie
 3268        => AddFieldCore(fieldName, Constants.ObjectFieldType, subFields: subFields, metadata: metadata, action: action);
 269
 270    /// <summary>
 271    /// Adds a field with a specific type, predefined subfields, and a nested builder action for additional dynamic conf
 272    /// </summary>
 273    /// <param name="fieldName">The name of the parent field to add. Supports dotted notation for nested fields.</param>
 274    /// <param name="type">The GraphQL type of the parent field (typically an object type or array type).</param>
 275    /// <param name="subFields">Array of subfield names to add under this field. Each subfield will have a default Strin
 276    /// <param name="action">A delegate that receives a FieldBuilder instance for configuring additional nested fields w
 277    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 278    /// <exception cref="ArgumentNullException">Thrown when fieldName, type, subFields, or action is null.</exception>
 279    public FieldBuilder AddField(string fieldName, string type, string[] subFields, Action<FieldBuilder> action)
 6280        => AddFieldCore(fieldName, type, subFields: subFields, action: action);
 281
 282    /// <summary>
 283    /// Adds a field with a specific type, predefined subfields, metadata, and a nested builder action for additional dy
 284    /// </summary>
 285    /// <param name="fieldName">The name of the parent field to add. Supports dotted notation for nested fields.</param>
 286    /// <param name="type">The GraphQL type of the parent field (typically an object type or array type).</param>
 287    /// <param name="subFields">Array of subfield names to add under this field. Each subfield will have a default Strin
 288    /// <param name="metadata">Optional metadata dictionary to associate with the parent field for custom processing.</p
 289    /// <param name="action">A delegate that receives a FieldBuilder instance for configuring additional nested fields w
 290    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 291    /// <exception cref="ArgumentNullException">Thrown when fieldName, type, subFields, or action is null.</exception>
 292    public FieldBuilder AddField(string fieldName, string type, string[] subFields, Dictionary<string, object?>? metadat
 3293        => AddFieldCore(fieldName, type, subFields: subFields, metadata: metadata, action: action);
 294
 295    /// <summary>
 296    /// Adds a field with predefined subfields, GraphQL arguments, metadata, and a nested builder action for additional 
 297    /// The field will have an object type. This is the most comprehensive subFields + action overload.
 298    /// </summary>
 299    /// <param name="fieldName">The name of the parent field to add. Supports dotted notation for nested fields.</param>
 300    /// <param name="subFields">Array of subfield names to add under this field. Each subfield will have a default Strin
 301    /// <param name="arguments">GraphQL arguments for the parent field such as filters, pagination, or custom parameters
 302    /// <param name="metadata">Optional metadata dictionary to associate with the parent field for custom processing.</p
 303    /// <param name="action">A delegate that receives a FieldBuilder instance for configuring additional nested fields w
 304    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 305    /// <exception cref="ArgumentNullException">Thrown when fieldName, subFields, or action is null.</exception>
 306    public FieldBuilder AddField(string fieldName, string[] subFields, Dictionary<string, object?>? arguments, Dictionar
 3307        => AddFieldCore(fieldName, Constants.ObjectFieldType, arguments, subFields, metadata, action);
 308
 309    /// <summary>
 310    /// Args-first counterpart of <see cref="AddField(string, string[], Dictionary{string, object?}?, Dictionary{string,
 311    /// Mirrors the parameter order used by <see cref="QueryBuilder.AddField(string, Dictionary{string, object?}, string
 312    /// </summary>
 313    /// <param name="fieldName">The name of the parent field to add.</param>
 314    /// <param name="arguments">GraphQL arguments for the parent field.</param>
 315    /// <param name="subFields">Array of subfield names to add under this field.</param>
 316    /// <param name="metadata">Optional metadata dictionary to associate with the parent field.</param>
 317    /// <param name="action">A delegate that receives a FieldBuilder instance for configuring additional nested fields.<
 318    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 319    public FieldBuilder AddField(string fieldName, Dictionary<string, object?>? arguments, string[] subFields, Dictionar
 3320        => AddFieldCore(fieldName, Constants.ObjectFieldType, arguments, subFields, metadata, action);
 321
 322    /// <summary>
 323    /// Args-first variant of <see cref="AddField(string, string[], Dictionary{string, object?}?, Dictionary{string, obj
 324    /// without metadata. Convenience overload that passes <c>metadata: null</c>.
 325    /// </summary>
 326    /// <param name="fieldName">The name of the parent field to add.</param>
 327    /// <param name="arguments">GraphQL arguments for the parent field.</param>
 328    /// <param name="subFields">Array of subfield names to add under this field.</param>
 329    /// <param name="action">A delegate that receives a FieldBuilder instance for configuring additional nested fields.<
 330    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 331    public FieldBuilder AddField(string fieldName, Dictionary<string, object?>? arguments, string[] subFields, Action<Fi
 3332        => AddFieldCore(fieldName, Constants.ObjectFieldType, arguments, subFields, action: action);
 333
 334    /// <summary>
 335    /// Adds a field with a specific type, predefined subfields, GraphQL arguments, metadata, and a nested builder actio
 336    /// This is the ultimate comprehensive overload that supports all possible field configuration options, including bo
 337    /// </summary>
 338    /// <param name="fieldName">The name of the parent field to add. Supports dotted notation for nested fields.</param>
 339    /// <param name="type">The GraphQL type of the parent field (typically an object type or array type).</param>
 340    /// <param name="subFields">Array of subfield names to add under this field. Each subfield will have a default Strin
 341    /// <param name="arguments">GraphQL arguments for the parent field such as filters, pagination, or custom parameters
 342    /// <param name="metadata">Optional metadata dictionary to associate with the parent field for custom processing.</p
 343    /// <param name="action">A delegate that receives a FieldBuilder instance for configuring additional nested fields w
 344    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 345    /// <exception cref="ArgumentNullException">Thrown when fieldName, type, subFields, or action is null.</exception>
 346    /// <example>
 347    /// <code>
 348    /// builder.AddField("users", "[User]", new[] { "id", "email" },
 349    ///     new SortedDictionary&lt;string, object?&gt; { ["first"] = 10 },
 350    ///     new Dictionary&lt;string, object?&gt; { ["cached"] = true },
 351    ///     users => {
 352    ///         users.AddField("profile", "Profile", profile => {
 353    ///             profile.AddField("name");
 354    ///             profile.AddField("avatar", "String");
 355    ///         });
 356    ///     });
 357    /// </code>
 358    /// </example>
 359    public FieldBuilder AddField(string fieldName, string type, string[] subFields, Dictionary<string, object?>? argumen
 3360        => AddFieldCore(fieldName, type, arguments, subFields, metadata, action);
 361
 362    /// <summary>
 363    /// Adds a simple field with an optional type to the builder. This is the most basic overload for adding scalar fiel
 364    /// </summary>
 365    /// <param name="fieldName">The name of the field to add. Supports dotted notation for nested fields (e.g., "user.pr
 366    /// <param name="type">The GraphQL type of the field. Defaults to "String" if not specified. Common types include "S
 367    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 368    /// <exception cref="ArgumentNullException">Thrown when fieldName is null.</exception>
 369    /// <exception cref="ArgumentException">Thrown when fieldName is empty or whitespace.</exception>
 370    /// <example>
 371    /// <code>
 372    /// builder.AddField("name");                    // String field (default)
 373    /// builder.AddField("age", "Int");              // Integer field
 374    /// builder.AddField("isActive", "Boolean");     // Boolean field
 375    /// builder.AddField("user.profile.email");     // Nested field with dotted notation
 376    /// </code>
 377    /// </example>
 378    public FieldBuilder AddField(string fieldName, string type = Constants.DefaultFieldType)
 1188379        => AddFieldCore(fieldName, type);
 380
 381    /// <summary>
 382    /// Core method that handles all field addition logic - simplified and optimized
 383    /// </summary>
 384    /// <param name="fieldName">The name of the field.</param>
 385    /// <param name="type">The type of the field (optional).</param>
 386    /// <param name="arguments">The arguments for the field (optional).</param>
 387    /// <param name="subFields">The subfields for the field (optional).</param>
 388    /// <param name="metadata">The metadata for the field (optional).</param>
 389    /// <param name="action">The action to configure nested fields (optional).</param>
 390    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 391    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 392    private FieldBuilder AddFieldCore(string fieldName, string? type = null, Dictionary<string, object?>? arguments = nu
 393        string[]? subFields = null, Dictionary<string, object?>? metadata = null, Action<FieldBuilder>? action = null)
 394    {
 1749395        ValidateFieldNameSegments(fieldName.AsSpan());
 1746396        var fieldType = type ?? Constants.DefaultFieldType;
 1746397        var field = FieldFactory.GetOrAddField(_fieldDefinition, fieldName, fieldType, arguments, _fieldDefinition.Path,
 398
 399        // FAST PATH: Skip subFields processing if array is null or empty
 1746400        if (subFields?.Length > 0)
 401        {
 1044402            foreach (var subField in subFields)
 403            {
 363404                FieldFactory.GetOrAddField(field, subField, Constants.DefaultFieldType, null, field.Path);
 405            }
 406        }
 407
 408        // FAST PATH: Skip action processing if null
 1746409        if (action != null)
 410        {
 201411            var fieldBuilder = new FieldBuilder(field);
 201412            action(fieldBuilder);
 413            // GetOrAddField above already added `field` as a child of _fieldDefinition,
 414            // so _fieldDefinition._children is non-null at this point.
 201415            _fieldDefinition._children!.Set(field.Name, fieldBuilder._fieldDefinition);
 416        }
 417
 1746418        return this;
 419    }
 420
 421    /// <summary>
 422    /// Creates a new FieldBuilder instance for the specified field with type, arguments, and metadata.
 423    /// </summary>
 424    /// <param name="fieldDefinitions">The collection of existing field definitions.</param>
 425    /// <param name="fieldName">The name of the field to create.</param>
 426    /// <param name="type">The type of the field.</param>
 427    /// <param name="arguments">The arguments for the field.</param>
 428    /// <param name="metadata">The metadata for the field.</param>
 429    /// <returns>A new FieldBuilder instance.</returns>
 430    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 431    public static FieldBuilder Create(Dictionary<string, FieldDefinition> fieldDefinitions, string fieldName, string typ
 432    {
 433        // FAIL-FAST: Use empty arguments if null or empty
 23034434        var argumentsToUse = arguments is { Count: > 0 } ? arguments : null;
 435
 436        // Use FieldFactory for field creation
 23034437        var rootField = FieldFactory.GetOrAddField(fieldDefinitions, fieldName, type, argumentsToUse, null, metadata);
 438
 23034439        var fieldBuilder = new FieldBuilder(rootField);
 440
 23034441        return fieldBuilder;
 442    }
 443
 444    /// <summary>
 445    /// Creates a new FieldBuilder instance from an existing field definition.
 446    /// </summary>
 447    /// <param name="fieldDefinition">The field definition to create the builder from.</param>
 448    /// <returns>A new FieldBuilder instance.</returns>
 3449    public static FieldBuilder Create(FieldDefinition fieldDefinition) => new(fieldDefinition);
 450
 451    /// <summary>
 452    /// Builds and returns the final field definition.
 453    /// </summary>
 454    /// <returns>The constructed FieldDefinition.</returns>
 516455    public FieldDefinition Build() => _fieldDefinition;
 456
 457    /// <summary>
 458    /// Recursively creates and merges field definitions into the target field collection.
 459    /// This method handles field merging, argument consolidation, and nested field creation.
 460    /// </summary>
 461    /// <param name="fields">Target field collection</param>
 462    /// <param name="fieldDefinition">Field definition to create/merge</param>
 463    private static void RecursiveCreateField(Dictionary<string, FieldDefinition> fields, FieldDefinition fieldDefinition
 464    {
 54465        var parentField = FieldFactory.CreateOrMergeField(fields, fieldDefinition);
 466
 467        // Recursively process all child fields using direct span iteration (zero-alloc)
 54468        if (fieldDefinition._children != null)
 469        {
 42470            var childrenSpan = fieldDefinition._children.AsSpan();
 42471            parentField._children ??= new FieldChildren();
 42472            var parentChildren = parentField._children;
 168473            for (int i = 0; i < childrenSpan.Length; i++)
 474            {
 42475                RecursiveCreateField(parentChildren, childrenSpan[i]);
 476            }
 477        }
 54478    }
 479
 480    private static void RecursiveCreateField(FieldChildren children, FieldDefinition fieldDefinition)
 481    {
 186482        var parentField = FieldFactory.CreateOrMergeField(children, fieldDefinition);
 483
 484        // Recursively process all child fields using direct span iteration (zero-alloc)
 186485        if (fieldDefinition._children != null)
 486        {
 96487            var childrenSpan = fieldDefinition._children.AsSpan();
 96488            parentField._children ??= new FieldChildren();
 96489            var parentChildren = parentField._children;
 480490            for (int i = 0; i < childrenSpan.Length; i++)
 491            {
 144492                RecursiveCreateField(parentChildren, childrenSpan[i]);
 493            }
 494        }
 186495    }
 496
 497    internal static void Include(Dictionary<string, FieldDefinition> fields, FieldDefinition fieldDefinition)
 54498        => RecursiveCreateField(fields, fieldDefinition);
 499
 500    private static void ValidateFieldNameSegments(ReadOnlySpan<char> fieldName)
 501    {
 2511502        if (fieldName.IsEmpty)
 3503            throw new ArgumentException("Field name cannot be empty.", nameof(fieldName));
 504
 505        // Validate each dot-separated segment individually
 6366506        while (!fieldName.IsEmpty)
 507        {
 3861508            var dotIndex = fieldName.IndexOf('.');
 3861509            var segment = dotIndex >= 0 ? fieldName[..dotIndex] : fieldName;
 3861510            if (!segment.IsEmpty)
 3861511                Helpers.ValidateFieldName(segment);
 3858512            fieldName = dotIndex >= 0 ? fieldName[(dotIndex + 1)..] : ReadOnlySpan<char>.Empty;
 513        }
 2505514    }
 515
 516    /// <summary>
 517    /// Sets an alias for the current field.
 518    /// </summary>
 519    /// <param name="alias">The alias to set for the field.</param>
 520    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 521    public FieldBuilder WithAlias(string alias)
 522    {
 9523        _fieldDefinition = _fieldDefinition with { Alias = alias };
 524
 9525        return this;
 526    }
 527
 528    /// <summary>
 529    /// Sets the type for the current field.
 530    /// </summary>
 531    /// <param name="type">The type to set for the field.</param>
 532    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 533    public FieldBuilder WithType(string type)
 534    {
 3535        _fieldDefinition = _fieldDefinition with { Type = type };
 536
 3537        return this;
 538    }
 539
 540    /// <summary>
 541    /// Sets or merges metadata for the current field.
 542    /// </summary>
 543    /// <param name="metadata">The metadata to set or merge with existing metadata.</param>
 544    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 545    public FieldBuilder WithMetadata(Dictionary<string, object> metadata)
 546    {
 42547        var mergedMetadata = Helpers.MergeMetadata(_fieldDefinition._metadata, metadata);
 548
 42549        _fieldDefinition.Metadata = mergedMetadata;
 550
 42551        return this;
 552    }
 553
 554    /// <summary>
 555    /// Adds or updates an argument for the current field.
 556    /// </summary>
 557    /// <param name="key">The argument key.</param>
 558    /// <param name="value">The argument value.</param>
 559    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 560    public FieldBuilder Where(string key, object? value)
 561    {
 562        // Ensure that _arguments exist (lazy initialization)
 36563        if (_fieldDefinition._arguments is null)
 564        {
 21565            _fieldDefinition = _fieldDefinition with { _arguments = new(StringComparer.OrdinalIgnoreCase) };
 566        }
 567
 568        // Determine the final value: merge dictionaries if both are dictionaries, otherwise set/override
 36569        _fieldDefinition._arguments[key] = _fieldDefinition._arguments.TryGetValue(key, out var existingValue)
 36570            ? (existingValue, value) switch
 36571            {
 36572                // Both values are dictionaries - merge them recursively
 36573                (IDictionary<string, object?> existingDict, IDictionary<string, object?> newDict)
 6574                    => Helpers.MergeNullableDictionaries(existingDict, newDict),
 36575
 36576                // Not dictionaries or mixed types - override with new value
 6577                _ => value
 36578            }
 36579            : value; // Key doesn't exist - set the value
 580
 36581        return this;
 582    }
 583
 584    /// <summary>
 585    /// Adds an inline GraphQL fragment narrowing the current field's selection set to a
 586    /// concrete type. Renders as <c>... on TypeName { … }</c>. Use when the parent field's
 587    /// schema return type is a union or interface and you need fields that only exist on
 588    /// a specific implementation.
 589    /// </summary>
 590    /// <param name="typeName">The concrete GraphQL type to narrow to (e.g. <c>"Repository"</c>).
 591    /// Case-sensitive — emitted verbatim into the rendered GraphQL.</param>
 592    /// <param name="action">Builder action for the fragment's selection set. The builder it
 593    /// receives writes into the fragment, not the parent field.</param>
 594    /// <returns>The current FieldBuilder instance for method chaining.</returns>
 595    /// <remarks>
 596    /// Multiple <c>OnType("Repository", …)</c> calls on the same parent merge into one
 597    /// fragment definition (idempotent registration, then the lambda extends the existing
 598    /// selection set). This matches the GraphQL semantics where two adjacent inline fragments
 599    /// for the same type are equivalent to one combined fragment.
 600    /// </remarks>
 601    /// <exception cref="ArgumentException">Thrown when <paramref name="typeName"/> is null or whitespace.</exception>
 602    /// <exception cref="ArgumentNullException">Thrown when <paramref name="action"/> is null.</exception>
 603    public FieldBuilder OnType(string typeName, Action<FieldBuilder> action)
 604    {
 63605        if (string.IsNullOrWhiteSpace(typeName))
 606        {
 9607            throw new ArgumentException("Inline fragment type name cannot be null or whitespace.", nameof(typeName));
 608        }
 54609        ArgumentNullException.ThrowIfNull(action);
 610
 51611        var fragment = _fieldDefinition.GetOrAddInlineFragment(typeName);
 612
 613        // Build a synthetic field whose `_children` aliases the fragment's field store, so the
 614        // user's lambda body (which adds fields via the standard FieldBuilder.AddField overloads)
 615        // writes straight into the fragment without an extra copy step.
 51616        var fragmentSurface = new FieldDefinition(
 51617            name: $"__inline_fragment_{typeName}",
 51618            type: Constants.DefaultFieldType)
 51619        {
 51620            _children = fragment.GetOrCreateFieldsStore(),
 51621            _fragments = fragment._fragments,
 51622        };
 623
 51624        var inner = new FieldBuilder(fragmentSurface);
 51625        action(inner);
 626
 627        // The lambda may have created the nested-fragments map on the synthetic field; reflect
 628        // that back onto the fragment so future OnType calls on the same fragment pick it up.
 51629        fragment._fragments = fragmentSurface._fragments;
 630
 51631        return this;
 632    }
 633}

Methods/Properties

.ctor(NGql.Core.Abstractions.FieldDefinition)
AddField(NGql.Core.Abstractions.FieldDefinition)
AddField(System.String,System.String[],System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
AddField(System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.String[],System.Collections.Generic.Dictionary`2<System.String,System.Object>)
AddField(System.String,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
AddField(System.String,System.String,System.String[],System.Collections.Generic.Dictionary`2<System.String,System.Object>)
AddField(System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
AddField(System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
AddField(System.String,System.String,System.String[],System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
AddField(System.String,System.Action`1<NGql.Core.Builders.FieldBuilder>)
AddField(System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Action`1<NGql.Core.Builders.FieldBuilder>)
AddField(System.String,System.String,System.Action`1<NGql.Core.Builders.FieldBuilder>)
AddField(System.String,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Action`1<NGql.Core.Builders.FieldBuilder>)
AddField(System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Action`1<NGql.Core.Builders.FieldBuilder>)
AddField(System.String,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Action`1<NGql.Core.Builders.FieldBuilder>)
AddField(System.String,System.String[],System.Action`1<NGql.Core.Builders.FieldBuilder>)
AddField(System.String,System.String[],System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Action`1<NGql.Core.Builders.FieldBuilder>)
AddField(System.String,System.String,System.String[],System.Action`1<NGql.Core.Builders.FieldBuilder>)
AddField(System.String,System.String,System.String[],System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Action`1<NGql.Core.Builders.FieldBuilder>)
AddField(System.String,System.String[],System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Action`1<NGql.Core.Builders.FieldBuilder>)
AddField(System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.String[],System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Action`1<NGql.Core.Builders.FieldBuilder>)
AddField(System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.String[],System.Action`1<NGql.Core.Builders.FieldBuilder>)
AddField(System.String,System.String,System.String[],System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Action`1<NGql.Core.Builders.FieldBuilder>)
AddField(System.String,System.String)
AddFieldCore(System.String,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.String[],System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Action`1<NGql.Core.Builders.FieldBuilder>)
Create(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.String,System.String,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
Create(NGql.Core.Abstractions.FieldDefinition)
Build()
RecursiveCreateField(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,NGql.Core.Abstractions.FieldDefinition)
RecursiveCreateField(NGql.Core.Abstractions.FieldChildren,NGql.Core.Abstractions.FieldDefinition)
Include(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,NGql.Core.Abstractions.FieldDefinition)
ValidateFieldNameSegments(System.ReadOnlySpan`1<System.Char>)
WithAlias(System.String)
WithType(System.String)
WithMetadata(System.Collections.Generic.Dictionary`2<System.String,System.Object>)
Where(System.String,System.Object)
OnType(System.String,System.Action`1<NGql.Core.Builders.FieldBuilder>)