Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ public bool IsPure
ResolverParameterKind.Parent or
ResolverParameterKind.Service or
ResolverParameterKind.GetGlobalState or
ResolverParameterKind.SetGlobalState or
ResolverParameterKind.GetScopedState or
ResolverParameterKind.HttpContext or
ResolverParameterKind.HttpRequest or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,9 @@ public static IRequestExecutorBuilder AddResolver(
/// A predicate that can be used to specify to which parameter the
/// expression shall be applied to.
/// </param>
/// <param name="isPure">
/// Defines if the parameter expression can be used for pure resolvers.
/// </param>
/// <typeparam name="T">
/// The parameter result type.
/// </typeparam>
Expand All @@ -479,20 +482,21 @@ public static IRequestExecutorBuilder AddResolver(
public static IRequestExecutorBuilder AddParameterExpressionBuilder<T>(
this IRequestExecutorBuilder builder,
Expression<Func<IResolverContext, T>> expression,
Func<ParameterInfo, bool>? canHandle = null)
Func<ParameterInfo, bool>? canHandle = null,
bool isPure = true)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(expression);

if (canHandle is null)
{
builder.Services.AddParameterExpressionBuilder(
_ => new CustomParameterExpressionBuilder<T>(expression));
_ => new CustomParameterExpressionBuilder<T>(expression, isPure));
}
else
{
builder.Services.AddParameterExpressionBuilder(
_ => new CustomParameterExpressionBuilder<T>(expression, canHandle));
_ => new CustomParameterExpressionBuilder<T>(expression, canHandle, isPure));
}

return builder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,29 @@ namespace HotChocolate.Internal;
/// </summary>
public abstract class CustomParameterExpressionBuilder : IParameterExpressionBuilder
{
private readonly bool _isPure;

/// <summary>
/// Initializes a new instance of <see cref="CustomParameterExpressionBuilder"/>
/// that is not considered pure.
/// </summary>
protected CustomParameterExpressionBuilder() { }

/// <summary>
/// Initializes a new instance of <see cref="CustomParameterExpressionBuilder"/>
/// with an explicit purity setting.
/// </summary>
/// <param name="isPure">
/// Defines if the parameter expression can be used for pure resolvers.
/// </param>
internal CustomParameterExpressionBuilder(bool isPure)
{
_isPure = isPure;
}

ArgumentKind IParameterExpressionBuilder.Kind => ArgumentKind.Custom;

bool IParameterExpressionBuilder.IsPure => false;
bool IParameterExpressionBuilder.IsPure => _isPure;

bool IParameterExpressionBuilder.IsDefaultHandler => false;

Expand Down Expand Up @@ -58,6 +78,25 @@ public class CustomParameterExpressionBuilder<TArg> : CustomParameterExpressionB
/// </param>
public CustomParameterExpressionBuilder(
Expression<Func<IResolverContext, TArg>> expression)
: base(isPure: false)
{
_canHandle = p => p.ParameterType == typeof(TArg);
_expression = expression;
}

/// <summary>
/// Initializes a new instance of <see cref="CustomParameterExpressionBuilder"/>.
/// </summary>
/// <param name="expression">
/// The expression that shall be used to resolve the parameter value.
/// </param>
/// <param name="isPure">
/// Defines if the parameter expression can be used for pure resolvers.
/// </param>
internal CustomParameterExpressionBuilder(
Expression<Func<IResolverContext, TArg>> expression,
bool isPure)
: base(isPure)
{
_canHandle = p => p.ParameterType == typeof(TArg);
_expression = expression;
Expand All @@ -75,6 +114,29 @@ public CustomParameterExpressionBuilder(
public CustomParameterExpressionBuilder(
Expression<Func<IResolverContext, TArg>> expression,
Func<ParameterInfo, bool> canHandle)
: base(isPure: false)
{
_expression = expression;
_canHandle = canHandle;
}

/// <summary>
/// Initializes a new instance of <see cref="CustomParameterExpressionBuilder"/>.
/// </summary>
/// <param name="canHandle">
/// A func that defines if a parameter can be handled by this expression builder.
/// </param>
/// <param name="expression">
/// The expression that shall be used to resolve the parameter value.
/// </param>
/// <param name="isPure">
/// Defines if the parameter expression can be used for pure resolvers.
/// </param>
internal CustomParameterExpressionBuilder(
Expression<Func<IResolverContext, TArg>> expression,
Func<ParameterInfo, bool> canHandle,
bool isPure)
: base(isPure)
{
_expression = expression;
_canHandle = canHandle;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,16 @@ private bool IsPureResolver(

if (!builder.IsPure)
{
// We allow scoped state getters to be considered pure because
// PureResolverContext can read ScopedContextData (it delegates
// to its parent). Setters and local state remain not pure.
if (builder is ScopedStateParameterExpressionBuilder
and not LocalStateParameterExpressionBuilder
&& !ParameterExpressionBuilderHelpers.IsStateSetter(parameter.ParameterType))
{
continue;
}

return false;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public ParameterBinding(

public ArgumentKind Kind => _parent.Kind;

public bool IsPure => _parent.IsPure;
public bool IsPure => true;

public T Execute<T>(IResolverContext context)
=> context.GetScopedStateOrDefault<T>(_key, default!);
Expand Down
30 changes: 30 additions & 0 deletions src/HotChocolate/Core/test/Types.Tests/Resolvers/Issue7399Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using HotChocolate.Execution;
using HotChocolate.Execution.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace HotChocolate.Resolvers;

public class Issue7399Tests
{
[Fact]
public async Task AddParameterExpressionBuilder_For_GlobalState_Does_Not_AutoInject_Cost()
{
var executor = await new ServiceCollection()
.AddGraphQLServer()
.AddQueryType<Query>()
.AddParameterExpressionBuilder(
ctx => ctx.GetGlobalState<MyGlobalState>("MyGlobalState"))
.BuildRequestExecutorAsync();

Assert.DoesNotContain("@cost(", executor.Schema.ToString(), StringComparison.Ordinal);
}

public sealed record MyGlobalState;

public sealed record MyThing(string Id);

public sealed class Query
{
public MyThing Test(MyGlobalState state) => new("Foo");
}
}
Loading