Skip to content

Commit

Permalink
Added Parent Projection Requirements (#7431)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstaib authored Sep 5, 2024
1 parent 90e2826 commit 8929302
Show file tree
Hide file tree
Showing 30 changed files with 865 additions and 108 deletions.
6 changes: 5 additions & 1 deletion src/HotChocolate/Core/src/Abstractions/ParentAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ namespace HotChocolate;
/// Specifies that a resolver parameter represents the parent object.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class ParentAttribute : Attribute
public sealed class ParentAttribute(string? requires = null) : Attribute
{
/// <summary>
/// Gets a string representing the property requirements for the parent object.
/// </summary>
public string? Requires { get; } = requires;
}
24 changes: 18 additions & 6 deletions src/HotChocolate/Core/src/Abstractions/WellKnownContextData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,12 +209,6 @@ public static class WellKnownContextData
/// </summary>
public const string SkipDepthAnalysis = "HotChocolate.Execution.SkipDepthAnalysis";

/// <summary>
/// The key of the marker setting that a field on the mutation type represents
/// the query field.
/// </summary>
public const string MutationQueryField = "HotChocolate.Relay.Mutations.QueryField";

/// <summary>
/// The key to the name of the data field when using the mutation convention.
/// </summary>
Expand Down Expand Up @@ -315,5 +309,23 @@ public static class WellKnownContextData
/// </summary>
public const string ValidateCost = "HotChocolate.CostAnalysis.ValidateCost";

/// <summary>
/// The key to access the paging observers stored on the local resolver state.
/// </summary>
public const string PagingObserver = "HotChocolate.Types.PagingObserver";

/// <summary>
/// The key to access the requirements syntax on an object field definition.
/// </summary>
public const string FieldRequirementsSyntax = "HotChocolate.Types.ObjectField.Requirements.Syntax";

/// <summary>
/// The key to access the requirements entity type on an object field definition.
/// </summary>
public const string FieldRequirementsEntity = "HotChocolate.Types.ObjectField.Requirements.EntityType";

/// <summary>
/// The key to access the compiled requirements.
/// </summary>
public const string FieldRequirements = "HotChocolate.Types.ObjectField.Requirements";
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
using HotChocolate.Execution.Configuration;
using HotChocolate.Execution.Options;
using HotChocolate.Execution.Processing;
#if NET6_0_OR_GREATER
using HotChocolate.Execution.Projections;
#endif
using HotChocolate.Fetching;
using HotChocolate.Internal;
using HotChocolate.Language;
Expand Down Expand Up @@ -123,12 +126,8 @@ public static IRequestExecutorBuilder AddGraphQL(
throw new ArgumentNullException(nameof(services));
}

services.AddGraphQLCore();
schemaName ??= Schema.DefaultName;

services
.AddGraphQLCore()
.AddValidation(schemaName);

return CreateBuilder(services, schemaName);
}

Expand All @@ -155,9 +154,6 @@ public static IRequestExecutorBuilder AddGraphQL(
}

schemaName ??= Schema.DefaultName;

builder.Services.AddValidation(schemaName);

return CreateBuilder(builder.Services, schemaName);
}

Expand All @@ -167,6 +163,8 @@ private static IRequestExecutorBuilder CreateBuilder(
{
var builder = new DefaultRequestExecutorBuilder(services, schemaName);

builder.Services.AddValidation(schemaName);

builder.Configure(
(sp, e) =>
{
Expand All @@ -178,6 +176,9 @@ private static IRequestExecutorBuilder CreateBuilder(

builder.TryAddNoOpTransactionScopeHandler();
builder.TryAddTypeInterceptor<DataLoaderRootFieldTypeInterceptor>();
#if NET6_0_OR_GREATER
builder.TryAddTypeInterceptor<RequirementsTypeInterceptor>();
#endif

return builder;
}
Expand Down
6 changes: 5 additions & 1 deletion src/HotChocolate/Core/src/Execution/Processing/Operation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ public Operation(
string id,
DocumentNode document,
OperationDefinitionNode definition,
ObjectType rootType)
ObjectType rootType,
ISchema schema)
{
Id = id;
Document = document;
Definition = definition;
RootType = rootType;
Type = definition.Operation;
Schema = schema;

if (definition.Name?.Value is { } name)
{
Expand Down Expand Up @@ -57,6 +59,8 @@ public IReadOnlyList<IncludeCondition> IncludeConditions

public IReadOnlyDictionary<string, object?> ContextData => _contextData;

public ISchema Schema { get; }

public ISelectionSet GetSelectionSet(ISelection selection, IObjectType typeContext)
{
if (selection is null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ private Operation CreateOperation(OperationCompilerRequest request)
request.Id,
request.Document,
request.Definition,
request.RootType);
request.RootType,
request.Schema);

var variants = new SelectionVariants[_selectionVariants.Count];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public FieldDelegate CompileResolverPipeline(IObjectField field, FieldNode selec
/// </summary>
public IOperation CreateOperation()
{
var operation = new Operation(Id, Document, Definition, _rootType);
var operation = new Operation(Id, Document, Definition, _rootType, Schema);
operation.Seal(_contextData, _variants, _hasIncrementalParts, _includeConditions);
return operation;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#if NET6_0_OR_GREATER
using System.Collections.Immutable;
using HotChocolate.Types;

namespace HotChocolate.Execution.Projections;

internal sealed class FieldRequirementsMetadata
{
private readonly Dictionary<SchemaCoordinate, ImmutableArray<PropertyNode>> _allRequirements = new();
private bool _sealed;

public ImmutableArray<PropertyNode>? GetRequirements(IObjectField field)
=> _allRequirements.TryGetValue(field.Coordinate, out var requirements) ? requirements : null;

public void TryAddRequirements(SchemaCoordinate fieldCoordinate, ImmutableArray<PropertyNode> requirements)
{
if(_sealed)
{
throw new InvalidOperationException("The requirements are sealed.");
}

_allRequirements.TryAdd(fieldCoordinate, requirements);
}

public void Seal()
=> _sealed = true;
}
#endif
170 changes: 170 additions & 0 deletions src/HotChocolate/Core/src/Execution/Projections/PropertyNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#if NET6_0_OR_GREATER
using System.Reflection;

namespace HotChocolate.Execution.Projections;

internal sealed class PropertyNode : PropertyNodeContainer
{
public PropertyNode(PropertyInfo property, List<PropertyNode>? nodes = null)
: base(nodes)
{
Property = property;
IsArray = property.PropertyType.IsArray;

if (IsArray)
{
ElementType = property.PropertyType.GetElementType();
}
else
{
var collectionType = GetCollectionType(property.PropertyType);
if (collectionType != null)
{
IsCollection = true;
ElementType = collectionType.GetGenericArguments()[0];
}
else
{
IsCollection = false;
ElementType = null;
}
}
}

private PropertyNode(
PropertyInfo property,
List<PropertyNode>? nodes,
bool isArray,
bool isCollection,
Type? elementType)
: base(nodes)
{
Property = property;
IsArray = isArray;
IsCollection = isCollection;
ElementType = elementType;
}

public PropertyInfo Property { get; }

public bool IsCollection { get; }

public bool IsArray { get; }

public bool IsArrayOrCollection => IsArray || IsCollection;

public Type? ElementType { get; }

public PropertyNode Clone()
{
List<PropertyNode>? nodes = null;

if (Nodes.Count > 0)
{
nodes = new(Nodes.Count);
foreach (var node in Nodes)
{
nodes.Add(node.Clone());
}
}

return new PropertyNode(Property, nodes, IsArray, IsCollection, ElementType);
}

private static Type? GetCollectionType(Type type)
{
if (type.IsGenericType
&& type.GetGenericTypeDefinition() == typeof(ICollection<>))
{
return type;
}

foreach (var interfaceType in type.GetInterfaces())
{
if (interfaceType.IsGenericType
&& interfaceType.GetGenericTypeDefinition() == typeof(ICollection<>))
{
return interfaceType;
}
}

return null;
}
}

internal class PropertyNodeContainer(
List<PropertyNode>? nodes = null)
: IPropertyNodeProvider
{
private static readonly IReadOnlyList<PropertyNode> _emptyNodes = Array.Empty<PropertyNode>();
private List<PropertyNode>? _nodes = nodes;
private bool _sealed;

public IReadOnlyList<PropertyNode> Nodes
=> _nodes ?? _emptyNodes;

public PropertyNode AddOrGetNode(PropertyInfo property)
{
if (_sealed)
{
throw new InvalidOperationException("The property node container is sealed.");
}

_nodes ??= new();

foreach (var node in Nodes)
{
if (node.Property.Name.Equals(property.Name))
{
return node;
}
}

var newNode = new PropertyNode(property);
_nodes.Add(newNode);
return newNode;
}

public void AddNode(PropertyNode newNode)
{
if (_sealed)
{
throw new InvalidOperationException("The property node container is sealed.");
}

_nodes ??= new();

foreach (var node in Nodes)
{
if (node.Property.Name.Equals(node.Property.Name))
{
throw new InvalidOperationException("Duplicate property.");
}
}

_nodes.Add(newNode);
}

public void Seal()
{
if (!_sealed)
{
foreach (var node in Nodes)
{
node.Seal();
}

_sealed = true;
}
}
}

internal interface IPropertyNodeProvider
{
IReadOnlyList<PropertyNode> Nodes { get; }

PropertyNode AddOrGetNode(PropertyInfo property);

void AddNode(PropertyNode newNode);
}
#endif
Loading

0 comments on commit 8929302

Please sign in to comment.