Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -77,11 +77,14 @@ public override bool TryHandleLeave(
[NotNullWhen(true)] out ISyntaxVisitorAction? action)
{
var runtimeType = context.RuntimeTypes.Pop();
context.TryCreateLambda(out var lambda);

if (context.TryCreateLambda(out var lambda))
context.Scopes.Pop();
var instance = context.PopInstance();

// an empty element filter produces no lambda, so the list operation adds no condition.
if (lambda is not null)
{
context.Scopes.Pop();
var instance = context.PopInstance();
var expression = HandleListOperation(
context,
field,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,17 @@ public override bool TryHandleLeave(
return false;
}

// Dequeue last
var condition = context.GetLevel().Dequeue();

context.PopInstance();
context.RuntimeTypes.Pop();

if (!context.GetLevel().TryDequeue(out var condition))
{
// an empty filter object produces no condition, so the field adds none.
action = SyntaxVisitor.Continue;

return true;
}

// When we are in an in-memory context, it is possible that we have null reference
// exceptions. To avoid these exceptions, we need to add null checks to the chain. We always
// wrap the field before in a null check.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,16 @@ protected override ISyntaxVisitorAction Enter(

if (context.Operations.Peek() is IFilterField field and not IOrField and not IAndField)
{
return OnFieldEnter(context, field, node);
var action = OnFieldEnter(context, field, node);

// a skipped field is never left, so the operation and type pushed on entering it are
// popped here.
if (action.Kind is SyntaxVisitorActionKind.Skip)
{
base.Leave(node, context);
}

return action;
}

return Continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,46 @@ await Snapshot
.MatchAsync(TestContext.Current.CancellationToken);
}

[Fact]
public async Task ExecuteAsync_Should_ApplyNoCondition_When_ElementFilterIsEmpty()
{
// arrange
var tester = _cache.CreateSchema<Foo, FooFilterInput>(s_fooEntities);

// act
var res1 = await tester.ExecuteAsync(
OperationRequestBuilder.New()
.SetDocument("{ root(where: { fooNested: { some: { } } }) { fooNested { bar } } }")
.Build(),
TestContext.Current.CancellationToken);
var res2 = await tester.ExecuteAsync(
OperationRequestBuilder.New()
.SetDocument("{ root(where: { fooNested: { all: { } } }) { fooNested { bar } } }")
.Build(),
TestContext.Current.CancellationToken);
var res3 = await tester.ExecuteAsync(
OperationRequestBuilder.New()
.SetDocument("{ root(where: { fooNested: { none: { } } }) { fooNested { bar } } }")
.Build(),
TestContext.Current.CancellationToken);
var res4 = await tester.ExecuteAsync(
OperationRequestBuilder.New()
.SetDocument(
"{ root(where: { fooNested: { some: { bar: { } } } }) "
+ "{ fooNested { bar } } }")
.Build(),
TestContext.Current.CancellationToken);

// assert
await Snapshot
.Create()
.Add(res1, "some")
.Add(res2, "all")
.Add(res3, "none")
.Add(res4, "some with empty field")
.MatchAsync(TestContext.Current.CancellationToken);
}

public class Foo
{
public int Id { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,49 @@ await Snapshot
.MatchAsync(TestContext.Current.CancellationToken);
}

[Fact]
public async Task ExecuteAsync_Should_ApplyNoCondition_When_FieldFilterIsEmpty()
{
// arrange
var tester = _cache.CreateSchema<BarNullable, BarNullableFilterInput>(
s_barNullableEntities);

// act
var res1 = await tester.ExecuteAsync(
OperationRequestBuilder.New()
.SetDocument("{ root(where: { foo: { } }) { foo { barShort } } }")
.Build(),
TestContext.Current.CancellationToken);
var res2 = await tester.ExecuteAsync(
OperationRequestBuilder.New()
.SetDocument("{ root(where: { foo: { barShort: { } } }) { foo { barShort } } }")
.Build(),
TestContext.Current.CancellationToken);
var res3 = await tester.ExecuteAsync(
OperationRequestBuilder.New()
.SetDocument(
"{ root(where: { foo: { barShort: { }, barBool: { eq: true } } }) "
+ "{ foo { barShort } } }")
.Build(),
TestContext.Current.CancellationToken);
var res4 = await tester.ExecuteAsync(
OperationRequestBuilder.New()
.SetDocument(
"{ root(where: { foo: { barBool: { eq: true }, barShort: { } } }) "
+ "{ foo { barShort } } }")
.Build(),
TestContext.Current.CancellationToken);

// assert
await Snapshot
.Create()
.Add(res1, "empty object")
.Add(res2, "empty operations")
.Add(res3, "empty field first")
.Add(res4, "empty field last")
.MatchAsync(TestContext.Current.CancellationToken);
}

public class Foo
{
public int Id { get; set; }
Expand Down
Loading
Loading