| | | 1 | | using System.Diagnostics.CodeAnalysis; |
| | | 2 | | using System.Text.Json.Serialization; |
| | | 3 | | |
| | | 4 | | namespace NGql.Core.Abstractions; |
| | | 5 | | |
| | | 6 | | /// <summary> |
| | | 7 | | /// Represents a GraphQL named fragment — a reusable selection set declared at the operation |
| | | 8 | | /// top level and referenced from one or more field selection sets via the <c>...Name</c> |
| | | 9 | | /// spread syntax. Renders as <c>fragment Name on TypeName { … }</c> after the operation |
| | | 10 | | /// block. |
| | | 11 | | /// </summary> |
| | | 12 | | /// <remarks> |
| | | 13 | | /// Named fragments live on <see cref="QueryDefinition.NamedFragments"/> (operation-scoped), |
| | | 14 | | /// not on <see cref="FieldDefinition.InlineFragments"/> (field-scoped). They are referenced |
| | | 15 | | /// from a field's selection set by adding the fragment's <see cref="Name"/> to |
| | | 16 | | /// <see cref="FieldDefinition.SpreadFragments"/>; the renderer emits <c>...Name</c> at |
| | | 17 | | /// each spread site. |
| | | 18 | | /// |
| | | 19 | | /// Naming follows the GraphQL spec: ASCII letters, digits, and underscores; first character |
| | | 20 | | /// must be a letter or underscore. Validation is intentionally minimal — invalid names |
| | | 21 | | /// render verbatim and the server rejects them with a clear error. |
| | | 22 | | /// </remarks> |
| | | 23 | | [SuppressMessage("Minor Code Smell", "S2292:Trivial properties should be auto-implemented")] |
| | | 24 | | public sealed record NamedFragmentDefinition |
| | | 25 | | { |
| | | 26 | | internal FieldChildren? _fields; |
| | | 27 | | internal Dictionary<string, InlineFragmentDefinition>? _fragments; |
| | | 28 | | internal List<string>? _spreadFragments; |
| | | 29 | | |
| | | 30 | | /// <summary> |
| | | 31 | | /// Creates a named fragment with the given <paramref name="name"/> on <paramref name="onType"/> |
| | | 32 | | /// and no children. |
| | | 33 | | /// </summary> |
| | | 34 | | /// <param name="name">The fragment's identifier, used at spread sites (<c>...Name</c>). |
| | | 35 | | /// Cannot be null or whitespace.</param> |
| | | 36 | | /// <param name="onType">The GraphQL type the fragment is declared on. Rendered verbatim |
| | | 37 | | /// after <c>fragment Name on </c>.</param> |
| | 276 | 38 | | public NamedFragmentDefinition(string name, string onType) |
| | | 39 | | { |
| | 276 | 40 | | if (string.IsNullOrWhiteSpace(name)) |
| | | 41 | | { |
| | 9 | 42 | | throw new ArgumentException("Named fragment name cannot be null or whitespace.", nameof(name)); |
| | | 43 | | } |
| | 267 | 44 | | if (string.IsNullOrWhiteSpace(onType)) |
| | | 45 | | { |
| | 9 | 46 | | throw new ArgumentException("Named fragment type name cannot be null or whitespace.", nameof(onType)); |
| | | 47 | | } |
| | | 48 | | |
| | 258 | 49 | | Name = name; |
| | 258 | 50 | | OnType = onType; |
| | 258 | 51 | | } |
| | | 52 | | |
| | | 53 | | /// <summary> |
| | | 54 | | /// The fragment's identifier. Used by spreads (<see cref="FieldDefinition.SpreadFragments"/>) |
| | | 55 | | /// to reference this fragment. |
| | | 56 | | /// </summary> |
| | | 57 | | [JsonPropertyName("name")] |
| | 504 | 58 | | public string Name { get; init; } |
| | | 59 | | |
| | | 60 | | /// <summary> |
| | | 61 | | /// The GraphQL type the fragment is declared on. Rendered verbatim after <c>fragment Name on </c>. |
| | | 62 | | /// </summary> |
| | | 63 | | [JsonPropertyName("onType")] |
| | 507 | 64 | | public string OnType { get; init; } |
| | | 65 | | |
| | | 66 | | /// <summary> |
| | | 67 | | /// Fields selected by this fragment. |
| | | 68 | | /// </summary> |
| | | 69 | | [JsonPropertyName("fields")] |
| | | 70 | | public IReadOnlyDictionary<string, FieldDefinition> Fields |
| | 15 | 71 | | => (IReadOnlyDictionary<string, FieldDefinition>?)_fields ?? EmptyFields; |
| | | 72 | | |
| | | 73 | | /// <summary> |
| | | 74 | | /// Get-or-add the underlying <see cref="FieldChildren"/> store. Internal so the builder |
| | | 75 | | /// can pass a stable reference when the user's lambda adds fields to the fragment. |
| | | 76 | | /// </summary> |
| | | 77 | | internal FieldChildren GetOrCreateFieldsStore() |
| | 204 | 78 | | => _fields ??= new FieldChildren(); |
| | | 79 | | |
| | | 80 | | /// <summary> |
| | | 81 | | /// Inline fragments inside this named fragment's selection set. Useful when the fragment's |
| | | 82 | | /// fields themselves return union/interface types that need further narrowing. |
| | | 83 | | /// </summary> |
| | | 84 | | [JsonPropertyName("inlineFragments")] |
| | | 85 | | public IReadOnlyDictionary<string, InlineFragmentDefinition> InlineFragments |
| | 12 | 86 | | => (IReadOnlyDictionary<string, InlineFragmentDefinition>?)_fragments ?? EmptyFragments; |
| | | 87 | | |
| | | 88 | | /// <summary> |
| | | 89 | | /// Names of other <see cref="NamedFragmentDefinition"/>s spread inside this fragment's |
| | | 90 | | /// selection set. See <see cref="FieldDefinition.SpreadFragments"/> for the contract. |
| | | 91 | | /// </summary> |
| | | 92 | | [JsonPropertyName("spreadFragments")] |
| | | 93 | | public IReadOnlyList<string> SpreadFragments |
| | 6 | 94 | | => (IReadOnlyList<string>?)_spreadFragments ?? EmptySpreadFragments; |
| | | 95 | | |
| | 3 | 96 | | private static readonly IReadOnlyDictionary<string, FieldDefinition> EmptyFields = new Dictionary<string, FieldDefin |
| | 3 | 97 | | private static readonly IReadOnlyDictionary<string, InlineFragmentDefinition> EmptyFragments = new Dictionary<string |
| | 3 | 98 | | private static readonly IReadOnlyList<string> EmptySpreadFragments = Array.Empty<string>(); |
| | | 99 | | |
| | | 100 | | public bool Equals(NamedFragmentDefinition? other) |
| | | 101 | | { |
| | 24 | 102 | | if (other is null) return false; |
| | 21 | 103 | | if (ReferenceEquals(this, other)) return true; |
| | 15 | 104 | | return string.Equals(Name, other.Name, StringComparison.Ordinal); |
| | | 105 | | } |
| | | 106 | | |
| | 6 | 107 | | public override int GetHashCode() => StringComparer.Ordinal.GetHashCode(Name); |
| | | 108 | | } |