From 0ba3efac901cde21012af9df43f55ea9efb0cdc8 Mon Sep 17 00:00:00 2001 From: James Dibble Date: Fri, 25 Jul 2025 22:13:03 +0100 Subject: [PATCH] Project multiple times with on --- .../TranslationProviderTests.Inheritance.cs | 104 +++++++++ .../TranslationProviderTests.Operations.cs | 74 ++++++ .../TranslationProviderTests.Projection.cs | 55 +++++ .../TranslationProviderTests.Simple.cs | 30 +++ .../Translation/TranslationProviderTests.cs | 221 +----------------- ...oviderTests.MultipleProjectionsWithOn.snap | 31 +++ LinQL/SelectExtentions.cs | 114 ++++++++- 7 files changed, 413 insertions(+), 216 deletions(-) create mode 100644 LinQL.Tests/Translation/TranslationProviderTests.Inheritance.cs create mode 100644 LinQL.Tests/Translation/TranslationProviderTests.Operations.cs create mode 100644 LinQL.Tests/Translation/TranslationProviderTests.Projection.cs create mode 100644 LinQL.Tests/Translation/TranslationProviderTests.Simple.cs create mode 100644 LinQL.Tests/Translation/__snapshots__/TranslationProviderTests.MultipleProjectionsWithOn.snap diff --git a/LinQL.Tests/Translation/TranslationProviderTests.Inheritance.cs b/LinQL.Tests/Translation/TranslationProviderTests.Inheritance.cs new file mode 100644 index 0000000..9a12d6f --- /dev/null +++ b/LinQL.Tests/Translation/TranslationProviderTests.Inheritance.cs @@ -0,0 +1,104 @@ +#pragma warning disable CA1852 // Seal internal types +#nullable enable +namespace LinQL.Tests.Translation; + +using LinQL.Description; + +public partial class TranslationProviderTests +{ + [Fact] + public void SimpleSelectInterface() => this.Test(x => x.SimpleType!.Number); + + [Fact] + public void SelectInterfaceCastConcreteType() + => this.Test(x => ((SomeOtherSimpleType)x.SimpleType!)!.Float); + + [Fact] + public void SelectAllFieldsInterfaceCastConcreteType() + => this.Test(x => (SomeOtherSimpleType)x.SimpleType!); + + [Fact] + public void SelectInterfaceAsConcreteType() + => this.Test(x => (x.SimpleType as SomeOtherSimpleType)!.Float); + + [Fact] + public void SelectAllFieldsInterfaceAsConcreteType() + => this.Test(x => (x.SimpleType as SomeOtherSimpleType)!); + + [Fact] + public void SelectAllFieldsInterfaceOnConcreteType() + => this.Test(x => + x.SimpleType.On((SomeOtherSimpleType y) => y.Number.ToString()) + .On((SimpleScalarType y) => y.Text!)); + + [Fact] + public void SelectInterfaceArray() + => this.Test(x => x.ArrayOfInterfaces.OfType().Select(y => new { y.Number, y.Text, y.Float })); + + [Fact] + public void SelectInterfaceArrayAllFieldsOnType() + => this.Test(x => x.ArrayOfInterfaces.OfType()); + + [Fact] + public void SelectInterfaceArrayCastConcreteType() + => this.Test(x => x.ArrayOfInterfaces.Select(y => new { ((SomeOtherSimpleType)y).Float, y.Text })); + + [Fact] + public void SelectInterfaceArrayAsConcreteType() + => this.Test(x => x.ArrayOfInterfaces.Select(y => new { (y as SomeOtherSimpleType)!.Float, y.Text })); + + [Fact] + public void SelectInterfaceArrayAsConcreteTypeWithOperation() + => this.Test(x => x.ArrayOfInterfaces.Select(y => new { Number = (y as SomeOtherSimpleType)!.Operation.GetNumber("123"), y.Text })); + + [Fact] + public void SelectInterfaceArrayOnConcreteType() + => this.Test(x => x.ArrayOfInterfaces + .Select(y => y.On((SimpleScalarType z) => z.Text) + .On((SomeOtherSimpleType z) => z.Float.ToString()!))); + + [Fact] + public void SelectInterfaceArrayMultipleTypes() + => this.Test(x => new + { + Types = x.ArrayOfInterfaces.OfType().Select(y => new { y.Number, y.Text }), + OtherTypes = x.ArrayOfInterfaces.OfType().Select(y => new { y.Number, y.Text, y.Float }), + }); + + [Fact] + public void SelectInterfaceArrayMultipleTypesSelectAll() + => this.Test(x => new + { + Types = x.ArrayOfInterfaces.OfType().Select(y => y.SelectAll()), + OtherTypes = x.ArrayOfInterfaces.OfType().Select(y => y.SelectAll()), + }); + + [Fact] + public void MultipleProjectionsWithOn() + => this.Test( + x => x.IAmObject + .On((Implementaion1 y) => y.Text) + .On((Implementaion2 y) => y.Value) + .On((Implementaion3 y) => y.Date) + .On((Implementaion4 y) => y.Amount) + .On((Implementaion5 y) => new { y.Id, Texts = y.Collection.Select(z => z.Text) }) + ); + + [OperationType] + private class MultipleTypeInheritence : RootType + { + public IAmInterface IAmObject { get; set; } = default!; + } + + private interface IAmInterface; + + private record Implementaion1(string Text) : IAmInterface; + + private record Implementaion2(int Value) : IAmInterface; + + private record Implementaion3(DateOnly Date) : IAmInterface; + + private record Implementaion4(float Amount) : IAmInterface; + + private record Implementaion5(Guid Id, List Collection) : IAmInterface; +} \ No newline at end of file diff --git a/LinQL.Tests/Translation/TranslationProviderTests.Operations.cs b/LinQL.Tests/Translation/TranslationProviderTests.Operations.cs new file mode 100644 index 0000000..1f9d372 --- /dev/null +++ b/LinQL.Tests/Translation/TranslationProviderTests.Operations.cs @@ -0,0 +1,74 @@ +#pragma warning disable CA1852 // Seal internal types +#nullable enable +namespace LinQL.Tests.Translation; + +public partial class TranslationProviderTests +{ + [Fact] + public void SimpleOperation() + => this.Test(x => new { x.Float, x.GetNumber().Number }); + + [Fact] + public void OperationWithConstantParameters() + => this.Test(x => new { x.Float, x.GetNumber("123").Number }); + + [Fact] + public void OperationWithObjectParameters() + => this.Test(x => new { x.Float, x.GetNumber(new SimpleScalarType()).Number }); + + [Fact] + public void OperationWithVariableMemberParameters() + { + var input = new SimpleScalarType { Number = 123, Text = "321" }; + + this.Test(x => new { x.GetNumber(input.Text).Number }); + } + + [Fact] + public void OperationWithVariableParameters() + { + var input = new SimpleScalarType { Number = 123, Text = "321" }; + + this.Test(x => new { x.GetNumber(input).Number }); + } + + [Fact] + public void OperationWithNullableVariableParameters() + { + var input = new SimpleScalarType { Number = 123, Text = "321" }; + + this.Test(x => new { x.GetNumber(input)!.Number }); + } + + [Fact] + public void NestedOperationWithVariableParameters() + { + var input = new SimpleScalarType { Number = 123, Text = "321" }; + + this.Test(x => new + { + x.Operation.GetNumber(input.Text).Number, + x.Text, + }); + } + + [Fact] + public void NestedOperationInOperation() + { + var input = new SimpleScalarType { Number = 123, Text = "321" }; + + this.Test(x => new + { + x.Operation(input.Number).Operation.GetNumber(input.Text).Number, + x.Text, + }); + } + + [Fact] + public void ScalarArrayOperation() + => this.Test(x => x.GetNumbers()); + + [Fact] + public void ScalarArrayOperationWithArguments() + => this.Test(x => x.GetFilteredNumbers(1)); +} diff --git a/LinQL.Tests/Translation/TranslationProviderTests.Projection.cs b/LinQL.Tests/Translation/TranslationProviderTests.Projection.cs new file mode 100644 index 0000000..669023d --- /dev/null +++ b/LinQL.Tests/Translation/TranslationProviderTests.Projection.cs @@ -0,0 +1,55 @@ +#pragma warning disable CA1852 // Seal internal types +#nullable enable +namespace LinQL.Tests.Translation; + +public partial class TranslationProviderTests +{ + [Fact] + public void SelectAllNestedFields() => this.Test(x => x.Nested!); + + [Fact] + public void SelectAllWithHelperNestedFields() => this.Test(x => x.Nested!.SelectAll()); + + [Fact] + public void SelectAllNestedFieldsFromOperation() => this.Test(x => x.Operation.GetNumber("123")!); + + [Fact] + public void SelectAllNestedFieldsWithHelperFromOperation() => this.Test(x => x.Operation.GetNumber("123")!.SelectAll()); + + [Fact] + public void SelectFromArray() => this.Test(x => x.Types.Select(x => new + { + x.Text, + x.Number, + })); + + [Fact] + public void RenameField() => this.Test(x => x.Text); + + [Fact] + public void RenameOperation() => this.Test(x => x.GetObject()); + + [Fact] + public void ProjectArrayOfInterface() + => this.Test(x => x.ArrayOfInterfaces.Select(a => new Projection(a.Number, a.Text))); + + [Fact] + public void ProjectIEnumerableOfInterface() + => this.Test(x => x.EnumerableOfInterfaces.Select(a => new Projection(a.Number, a.Text))); + + [Fact] + public void ProjectListOfInterface() + => this.Test(x => x.ListOfInterfaces.Select(a => new Projection(a.Number, a.Text))); + + [Fact] + public void Project() + => this.Test(x => x.Operation.GetNumber("project me").Project(y => new Projection(y.Number, y.Text))); + + [Fact] + public void ProjectAcrossMultipleLevels() + => this.Test(x => new Projection(x.Number, x.Operation.GetNumber("project me").Project(y => y.Text))); + + [Fact] + public void ProjectSameOperationMultipleTimes() + => this.Test(x => new NestedProjection(x.Operation.Float, x.Operation.GetNumber("project me").Project(y => new Projection(y.Number, y.Text)))); +} diff --git a/LinQL.Tests/Translation/TranslationProviderTests.Simple.cs b/LinQL.Tests/Translation/TranslationProviderTests.Simple.cs new file mode 100644 index 0000000..79fd83d --- /dev/null +++ b/LinQL.Tests/Translation/TranslationProviderTests.Simple.cs @@ -0,0 +1,30 @@ +#pragma warning disable CA1852 // Seal internal types +#nullable enable +namespace LinQL.Tests.Translation; + +public partial class TranslationProviderTests +{ + [Fact] + public void ScalarOnRoot() + => this.Test(x => x.GetNumber()); + + [Fact] + public void Simple() + => this.Test(x => new { x.Number, x.Text }); + + [Fact] + public void NestedMemberFlatten() + => this.Test(x => new { x.Float, x.Nested!.Number, x.Nested.Text }); + + [Fact] + public void NestedMember() + => this.Test(x => new { x.Float, Nested = new { x.Nested!.Number } }); + + [Fact] + public void BasicInclude() + => this.TestInclude(x => x.Operation, x => x.Include(y => y.Operation.GetNumber("123"))); + + [Fact] + public void ScalarArrayFields() + => this.Test(x => x.Strings); +} diff --git a/LinQL.Tests/Translation/TranslationProviderTests.cs b/LinQL.Tests/Translation/TranslationProviderTests.cs index 6fef33e..ba447e7 100644 --- a/LinQL.Tests/Translation/TranslationProviderTests.cs +++ b/LinQL.Tests/Translation/TranslationProviderTests.cs @@ -7,197 +7,12 @@ namespace LinQL.Tests.Translation; using LinQL.Description; using LinQL.Expressions; using LinQL.Translation; -using Xunit; -public class TranslationProviderTests +public partial class TranslationProviderTests { - [Fact] - public void ScalarOnRoot() - => this.Test(x => x.GetNumber()); - - [Fact] - public void Simple() - => this.Test(x => new { x.Number, x.Text }); - - [Fact] - public void NestedMemberFlatten() - => this.Test(x => new { x.Float, x.Nested!.Number, x.Nested.Text }); - - [Fact] - public void NestedMember() - => this.Test(x => new { x.Float, Nested = new { x.Nested!.Number } }); - - [Fact] - public void SimpleOperation() - => this.Test(x => new { x.Float, x.GetNumber().Number }); - - [Fact] - public void OperationWithConstantParameters() - => this.Test(x => new { x.Float, x.GetNumber("123").Number }); - - [Fact] - public void OperationWithObjectParameters() - => this.Test(x => new { x.Float, x.GetNumber(new SimpleScalarType()).Number }); - - [Fact] - public void OperationWithVariableMemberParameters() - { - var input = new SimpleScalarType { Number = 123, Text = "321" }; - - this.Test(x => new { x.GetNumber(input.Text).Number }); - } - - [Fact] - public void OperationWithVariableParameters() - { - var input = new SimpleScalarType { Number = 123, Text = "321" }; - - this.Test(x => new { x.GetNumber(input).Number }); - } - - [Fact] - public void OperationWithNullableVariableParameters() - { - var input = new SimpleScalarType { Number = 123, Text = "321" }; - - this.Test(x => new { x.GetNumber(input)!.Number }); - } - - [Fact] - public void NestedOperationWithVariableParameters() - { - var input = new SimpleScalarType { Number = 123, Text = "321" }; - - this.Test(x => new - { - x.Operation.GetNumber(input.Text).Number, - x.Text, - }); - } - - [Fact] - public void NestedOperationInOperation() - { - var input = new SimpleScalarType { Number = 123, Text = "321" }; - - this.Test(x => new - { - x.Operation(input.Number).Operation.GetNumber(input.Text).Number, - x.Text, - }); - } - - [Fact] - public void SelectAllNestedFields() => this.Test(x => x.Nested!); - - [Fact] - public void SelectAllWithHelperNestedFields() => this.Test(x => x.Nested!.SelectAll()); - - [Fact] - public void SelectAllNestedFieldsFromOperation() => this.Test(x => x.Operation.GetNumber("123")!); - - [Fact] - public void SelectAllNestedFieldsWithHelperFromOperation() => this.Test(x => x.Operation.GetNumber("123")!.SelectAll()); - - [Fact] - public void SelectFromArray() => this.Test(x => x.Types.Select(x => new - { - x.Text, - x.Number, - })); - - [Fact] - public void RenameField() => this.Test(x => x.Text); - - [Fact] - public void RenameOperation() => this.Test(x => x.GetObject()); - - [Fact] - public void SimpleSelectInterface() => this.Test(x => x.SimpleType!.Number); - - [Fact] - public void SelectInterfaceCastConcreteType() - => this.Test(x => ((SomeOtherSimpleType)x.SimpleType!)!.Float); - - [Fact] - public void SelectAllFieldsInterfaceCastConcreteType() - => this.Test(x => (SomeOtherSimpleType)x.SimpleType!); - - [Fact] - public void SelectInterfaceAsConcreteType() - => this.Test(x => (x.SimpleType as SomeOtherSimpleType)!.Float); - - [Fact] - public void SelectAllFieldsInterfaceAsConcreteType() - => this.Test(x => (x.SimpleType as SomeOtherSimpleType)!); - - [Fact] - public void SelectAllFieldsInterfaceOnConcreteType() - => this.Test(x => - x.SimpleType.On((SomeOtherSimpleType y) => y.Number.ToString()) - .On((SimpleScalarType y) => y.Text!)); - - [Fact] - public void SelectInterfaceArray() - => this.Test(x => x.ArrayOfInterfaces.OfType().Select(y => new { y.Number, y.Text, y.Float })); - - [Fact] - public void SelectInterfaceArrayAllFieldsOnType() - => this.Test(x => x.ArrayOfInterfaces.OfType()); - - [Fact] - public void SelectInterfaceArrayCastConcreteType() - => this.Test(x => x.ArrayOfInterfaces.Select(y => new { ((SomeOtherSimpleType)y).Float, y.Text })); - - [Fact] - public void SelectInterfaceArrayAsConcreteType() - => this.Test(x => x.ArrayOfInterfaces.Select(y => new { (y as SomeOtherSimpleType)!.Float, y.Text })); - - [Fact] - public void SelectInterfaceArrayAsConcreteTypeWithOperation() - => this.Test(x => x.ArrayOfInterfaces.Select(y => new { Number = (y as SomeOtherSimpleType)!.Operation.GetNumber("123"), y.Text })); - - [Fact] - public void SelectInterfaceArrayOnConcreteType() - => this.Test(x => x.ArrayOfInterfaces - .Select(y => y.On((SimpleScalarType z) => z.Text) - .On((SomeOtherSimpleType z) => z.Float.ToString()!))); - - [Fact] - public void SelectInterfaceArrayMultipleTypes() - => this.Test(x => new - { - Types = x.ArrayOfInterfaces.OfType().Select(y => new { y.Number, y.Text }), - OtherTypes = x.ArrayOfInterfaces.OfType().Select(y => new { y.Number, y.Text, y.Float }), - }); - - [Fact] - public void SelectInterfaceArrayMultipleTypesSelectAll() - => this.Test(x => new - { - Types = x.ArrayOfInterfaces.OfType().Select(y => y.SelectAll()), - OtherTypes = x.ArrayOfInterfaces.OfType().Select(y => y.SelectAll()), - }); - - private record Projection(int Number, string? Text); - - private record NestedProjection(float Float, Projection Projection); - - [Fact] - public void ProjectArrayOfInterface() - => this.Test(x => x.ArrayOfInterfaces.Select(a => new Projection(a.Number, a.Text))); - - [Fact] - public void ProjectIEnumerableOfInterface() - => this.Test(x => x.EnumerableOfInterfaces.Select(a => new Projection(a.Number, a.Text))); - - [Fact] - public void ProjectListOfInterface() - => this.Test(x => x.ListOfInterfaces.Select(a => new Projection(a.Number, a.Text))); - - [Fact] - public void BasicInclude() - => this.TestInclude(x => x.Operation, x => x.Include(y => y.Operation.GetNumber("123"))); + private void Test(Expression> expression) + where TRoot : RootType + => this.TestInclude(expression, _ => { }); private void TestInclude(Expression> expression, Action> includes) where TRoot : RootType @@ -207,21 +22,9 @@ private void TestInclude(Expression> expression Snapshot.Match(request.Query); } - [Fact] - public void Project() - => this.Test(x => x.Operation.GetNumber("project me").Project(y => new Projection(y.Number, y.Text))); - - [Fact] - public void ProjectAcrossMultipleLevels() - => this.Test(x => new Projection(x.Number, x.Operation.GetNumber("project me").Project(y => y.Text))); - - [Fact] - public void ProjectSameOperationMultipleTimes() - => this.Test(x => new NestedProjection(x.Operation.Float, x.Operation.GetNumber("project me").Project(y => new Projection(y.Number, y.Text)))); + private record Projection(int Number, string? Text); - private void Test(Expression> expression) - where TRoot : RootType - => this.TestInclude(expression, _ => { }); + private record NestedProjection(float Float, Projection Projection); [OperationType] private class SimpleScalarType : ISimpleType, RootType @@ -364,16 +167,4 @@ public class ScalarArray : RootType [GraphQLOperation, GraphQLField(Name = "filteredNumbers")] public int[] GetFilteredNumbers([GraphQLArgument(GQLType = "Int!")] int number) => [number]; } - - [Fact] - public void ScalarArrayFields() - => this.Test(x => x.Strings); - - [Fact] - public void ScalarArrayOperation() - => this.Test(x => x.GetNumbers()); - - [Fact] - public void ScalarArrayOperationWithArguments() - => this.Test(x => x.GetFilteredNumbers(1)); } diff --git a/LinQL.Tests/Translation/__snapshots__/TranslationProviderTests.MultipleProjectionsWithOn.snap b/LinQL.Tests/Translation/__snapshots__/TranslationProviderTests.MultipleProjectionsWithOn.snap new file mode 100644 index 0000000..5ed748a --- /dev/null +++ b/LinQL.Tests/Translation/__snapshots__/TranslationProviderTests.MultipleProjectionsWithOn.snap @@ -0,0 +1,31 @@ +query { + iAmObject { + __typename + ...on implementaion1 { + text + } + ...on implementaion2 { + value + } + ...on implementaion3 { + date { + year + month + day + dayOfWeek + dayOfYear + dayNumber + } + } + ...on implementaion4 { + amount + } + ...on implementaion5 { + id { + } + collection { + text + } + } + } +} diff --git a/LinQL/SelectExtentions.cs b/LinQL/SelectExtentions.cs index d5e82d7..6fe583d 100644 --- a/LinQL/SelectExtentions.cs +++ b/LinQL/SelectExtentions.cs @@ -35,10 +35,11 @@ public static OneOf On(this T that, ExpressionThe interface or union /// The type to spread as /// The result + /// The result /// The interface or union /// The expression to run over . /// The result or the original object if it isn't - public static OneOf On(this OneOf that, Expression> spread) + public static OneOf On(this OneOf that, Expression> spread) { if (that.IsT1) { @@ -53,6 +54,117 @@ public static OneOf On(this OneOf th return that.AsT0; } + /// + /// Spread a given type. + /// + /// The interface or union + /// The type to spread as + /// The result + /// The result + /// The result + /// The interface or union + /// The expression to run over . + /// The result or the original object if it isn't + public static OneOf On(this OneOf that, Expression> spread) + { + if (that.IsT1) + { + return that.AsT1; + } + + if (that.IsT2) + { + return that.AsT2; + } + + if (that.AsT0 is TMaybe target) + { + return spread.CompileFast()(target); + } + + return that.AsT0; + } + + /// + /// Spread a given type. + /// + /// The interface or union + /// The type to spread as + /// The result + /// The result + /// The result + /// The result + /// The interface or union + /// The expression to run over . + /// The result or the original object if it isn't + public static OneOf On(this OneOf that, Expression> spread) + { + if (that.IsT1) + { + return that.AsT1; + } + + if (that.IsT2) + { + return that.AsT2; + } + + if (that.IsT3) + { + return that.AsT3; + } + + if (that.AsT0 is TMaybe target) + { + return spread.CompileFast()(target); + } + + return that.AsT0; + } + + /// + /// Spread a given type. + /// + /// The interface or union + /// The type to spread as + /// The result + /// The result + /// The result + /// The result + /// The result + /// The interface or union + /// The expression to run over . + /// The result or the original object if it isn't + public static OneOf On(this OneOf that, Expression> spread) + { + if (that.IsT1) + { + return that.AsT1; + } + + if (that.IsT2) + { + return that.AsT2; + } + + if (that.IsT3) + { + return that.AsT3; + } + + if (that.IsT4) + { + return that.AsT4; + } + + if (that.AsT0 is TMaybe target) + { + return spread.CompileFast()(target); + } + + return that.AsT0; + } + /// /// Instruct the query to get all scalar fields on this type. ///