Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Parent Projection Requirements #7431

Merged
merged 5 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading