From c4860f315c7a21da4b69dadcb0e4ded8d7d08c2f Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Fri, 7 Mar 2025 23:19:08 +0200 Subject: [PATCH 01/23] Implemented ObjectListFilter tests for following cases: And, Equals, GreaterThan, LessThan, StartsWith, EndsWith, Contains --- .../ObjectListFilter.fs | 278 +++++++++++------- .../FSharp.Data.GraphQL.Shared.fsproj | 3 + tests/FSharp.Data.GraphQL.Tests/LinqTests.fs | 129 ++++++++ 3 files changed, 297 insertions(+), 113 deletions(-) diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs index e46edb7e0..472b60233 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs @@ -60,116 +60,168 @@ module ObjectListFilter = /// Creates a new ObjectListFilter representing a NOT opreation for the existing one. let ( !!! ) filter = Not filter -//[] -//module ObjectListFilterExtensions = - -// type ObjectListFilter with - -// member filter.Apply<'T, 'D>(query : IQueryable<'T>, -// compareDiscriminator : Expr<'T -> 'D -> 'D> | null, -// getDiscriminatorValue : (Type -> 'D) | null) = -// filter.Apply(query, compareDiscriminator, getDiscriminatorValue) - -// member filter.Apply<'T, 'D>(query : IQueryable<'T>, -// [] getDiscriminator : Expr<'T -> 'D> | null, -// [] getDiscriminatorValue : (Type -> 'D) | null) = -// // Helper to create parameter expression for the lambda -// let param = Expression.Parameter(typeof<'T>, "x") - -// // Helper to get property value -// let getPropertyExpr fieldName = -// Expression.PropertyOrField(param, fieldName) - -// // Helper to create lambda from body expression -// let makeLambda (body: Expression) = -// let delegateType = typedefof>.MakeGenericType([|typeof<'T>; body.Type|]) -// Expression.Lambda(delegateType, body, param) - -// // Helper to create Where expression -// let whereExpr predicate = -// let whereMethod = -// typeof.GetMethods() -// |> Seq.where (fun m -> m.Name = "Where") -// |> Seq.find (fun m -> -// let parameters = m.GetParameters() -// parameters.Length = 2 -// && parameters[1].ParameterType.GetGenericTypeDefinition() = typedefof>>) -// |> fun m -> m.MakeGenericMethod([|typeof<'T>|]) -// Expression.Call(whereMethod, [|query.Expression; makeLambda predicate|]) - -// // Helper for discriminator comparison -// let buildTypeDiscriminatorCheck (t: Type) = -// match getDiscriminator, getDiscriminatorValue with -// | null, _ | _, null -> None -// | discExpr, discValueFn -> -// let compiled = QuotationEvaluator.Eval(discExpr) -// let discriminatorValue = discValueFn t -// let discExpr = getPropertyExpr "__discriminator" // Assuming discriminator field name -// let valueExpr = Expression.Constant(discriminatorValue) -// Some(Expression.Equal(discExpr, valueExpr)) - -// // Main filter logic -// let rec buildFilterExpr filter = -// match filter with -// | NoFilter -> query.Expression -// | And (f1, f2) -> -// let q1 = buildFilterExpr f1 |> Expression.Lambda>>|> _.Compile().Invoke() -// buildFilterExpr f2 |> Expression.Lambda>> |> _.Compile().Invoke(q1).Expression -// | Or (f1, f2) -> -// let expr1 = buildFilterExpr f1 -// let expr2 = buildFilterExpr f2 -// let unionMethod = -// typeof.GetMethods() -// |> Array.find (fun m -> m.Name = "Union") -// |> fun m -> m.MakeGenericMethod([|typeof<'T>|]) -// Expression.Call(unionMethod, [|expr1; expr2|]) -// | Not f -> -// let exceptMethod = -// typeof.GetMethods() -// |> Array.find (fun m -> m.Name = "Except") -// |> fun m -> m.MakeGenericMethod([|typeof<'T>|]) -// Expression.Call(exceptMethod, [|query.Expression; buildFilterExpr f|]) -// | Equals f -> -// Expression.Equal(getPropertyExpr f.FieldName, Expression.Constant(f.Value)) |> whereExpr -// | GreaterThan f -> -// Expression.GreaterThan(getPropertyExpr f.FieldName, Expression.Constant(f.Value)) |> whereExpr -// | LessThan f -> -// Expression.LessThan(getPropertyExpr f.FieldName, Expression.Constant(f.Value)) |> whereExpr -// | StartsWith f -> -// let methodInfo = typeof.GetMethod("StartsWith", [|typeof|]) -// Expression.Call(getPropertyExpr f.FieldName, methodInfo, Expression.Constant(f.Value)) |> whereExpr -// | EndsWith f -> -// let methodInfo = typeof.GetMethod("EndsWith", [|typeof|]) -// Expression.Call(getPropertyExpr f.FieldName, methodInfo, Expression.Constant(f.Value)) |> whereExpr -// | Contains f -> -// let methodInfo = typeof.GetMethod("Contains", [|typeof|]) -// Expression.Call(getPropertyExpr f.FieldName, methodInfo, Expression.Constant(f.Value)) |> whereExpr -// | OfTypes types -> -// match types.Value with -// | [] -> query.Expression // No types specified, return original query -// | types -> -// let typeChecks = -// types -// |> List.choose buildTypeDiscriminatorCheck -// |> List.fold (fun acc expr -> -// match acc with -// | None -> Some expr -// | Some prevExpr -> Some(Expression.OrElse(prevExpr, expr))) None - -// match typeChecks with -// | None -> query.Expression -// | Some expr -> whereExpr expr -// | FilterField f -> -// let propExpr = getPropertyExpr f.FieldName -// match propExpr.Type.GetInterfaces() -// |> Array.tryFind (fun t -> -// t.IsGenericType && t.GetGenericTypeDefinition() = typedefof>) with -// | Some queryableType -> -// let elementType = queryableType.GetGenericArguments().[0] -// let subFilter = f.Value -// let subQuery = Expression.Convert(propExpr, queryableType) -// Expression.Call(typeof, "Any", [|elementType|], subQuery) |> whereExpr -// | None -> query.Expression - -// // Create and execute the final expression -// query.Provider.CreateQuery<'T>(buildFilterExpr filter) +[] +module ObjectListFilterExtensions = + + //// discriminator custom condition + //let a () = + // filter.Apply( + // queryable, + // <@ fun e d -> e.Discriminator.StartsWith d @>, + // function + // | t when Type.(=)(t, typeof) -> ResidentialPropertiesConstants.Discriminators.Complex + // | t when Type.(=)(t, typeof) -> ResidentialPropertiesConstants.Discriminators.Building + // ) + //// discriminator equals + //let b () = + // filter.Apply( + // queryable, + // <@ fun e -> e.Discriminator @>, + // function + // | t when Type.(=)(t, typeof) -> ResidentialPropertiesConstants.Discriminators.Complex + // | t when Type.(=)(t, typeof) -> ResidentialPropertiesConstants.Discriminators.Building + // ) + + // Helper to create parameter expression for the lambda + let param<'T> = Expression.Parameter(typeof<'T>, "x") + + // Helper to get property value + let getPropertyExpr<'T> (param: ParameterExpression) fieldName = + Expression.PropertyOrField(param, fieldName) + + // Helper to create lambda from body expression + let makeLambda<'T> (param: ParameterExpression) (body: Expression) = + let delegateType = typedefof>.MakeGenericType([|typeof<'T>; body.Type|]) + Expression.Lambda(delegateType, body, param) + + // Helper to create Where expression + let whereExpr<'T> (query : IQueryable<'T>) (param: ParameterExpression) predicate = + let whereMethod = + typeof.GetMethods() + |> Seq.where (fun m -> m.Name = "Where") + |> Seq.find (fun m -> + let parameters = m.GetParameters() + parameters.Length = 2 + && parameters[1].ParameterType.GetGenericTypeDefinition() = typedefof>>) + |> fun m -> m.MakeGenericMethod([|typeof<'T>|]) + Expression.Call(whereMethod, [|query.Expression; makeLambda<'T> param predicate|]) + + // Main filter logic + let rec buildFilterExpr<'T> (param: ParameterExpression) buildTypeDiscriminatorCheck filter (query : IQueryable<'T>) = + let buildFilterExpr = buildFilterExpr<'T> param buildTypeDiscriminatorCheck + match filter with + | NoFilter -> query.Expression + | And (f1, f2) -> + let q1 = buildFilterExpr f1 query |> Expression.Lambda>> |> _.Compile().Invoke() + let q2 = buildFilterExpr f2 q1 |> Expression.Lambda>> |> _.Compile().Invoke() + q2.Expression + //| Or (f1, f2) -> + // let expr1 = buildFilterExpr f1 query + // let expr2 = buildFilterExpr f2 query + // Expression.OrElse(expr1, expr2) |> whereExpr<'T> query param :> Expression + //| Not f -> + // let exceptMethod = + // typeof.GetMethods() + // |> Array.find (fun m -> m.Name = "Except") + // |> fun m -> m.MakeGenericMethod([|typeof<'T>|]) + // Expression.Call(exceptMethod, [|query.Expression; buildFilterExpr f|]) + //| OfTypes types -> + // match types.Value with + // | [] -> query.Expression // No types specified, return original query + // | types -> + // let typeChecks = + // types + // |> List.vchoose buildTypeDiscriminatorCheck + // |> List.fold (fun acc expr -> + // match acc with + // | ValueNone -> ValueSome expr + // | ValueSome prevExpr -> ValueSome (Expression.OrElse(prevExpr, expr))) ValueNone + + // match typeChecks with + // | ValueNone -> query.Expression + // | ValueSome expr -> whereExpr query expr :> Expression + | Equals f -> + Expression.Equal(getPropertyExpr<'T> param f.FieldName, Expression.Constant(f.Value)) |> whereExpr<'T> query param :> Expression + | GreaterThan f -> + Expression.GreaterThan(getPropertyExpr<'T> param f.FieldName, Expression.Constant(f.Value)) |> whereExpr<'T> query param :> Expression + | LessThan f -> + Expression.LessThan(getPropertyExpr<'T> param f.FieldName, Expression.Constant(f.Value)) |> whereExpr<'T> query param :> Expression + | StartsWith f -> + let methodInfo = typeof.GetMethod("StartsWith", [|typeof|]) + Expression.Call(getPropertyExpr<'T> param f.FieldName, methodInfo, Expression.Constant(f.Value)) |> whereExpr<'T> query param :> Expression + | EndsWith f -> + let methodInfo = typeof.GetMethod("EndsWith", [|typeof|]) + Expression.Call(getPropertyExpr<'T> param f.FieldName, methodInfo, Expression.Constant(f.Value)) |> whereExpr<'T> query param :> Expression + | Contains f -> + let methodInfo = typeof.GetMethod("Contains", [|typeof|]) + Expression.Call(getPropertyExpr<'T> param f.FieldName, methodInfo, Expression.Constant(f.Value)) |> whereExpr<'T> query param :> Expression + | FilterField f -> + let propExpr = getPropertyExpr<'T> param f.FieldName + match propExpr.Type.GetInterfaces() + |> Array.tryFind (fun t -> + t.IsGenericType && t.GetGenericTypeDefinition() = typedefof>) with + | Some queryableType -> + let elementType = queryableType.GetGenericArguments().[0] + let subFilter = f.Value + let subQuery = Expression.Convert(propExpr, queryableType) + Expression.Call(typeof, "Any", [|elementType|], subQuery) |> whereExpr<'T> query param :> Expression + | None -> query.Expression + +type ObjectListFilter with + + member filter.Apply<'T, 'D>(query : IQueryable<'T>, + compareDiscriminator : Expression>, + getDiscriminatorValue : (Type -> 'D)) = + + // Helper for discriminator comparison + let buildTypeDiscriminatorCheck (t: Type) = + match compareDiscriminator, getDiscriminatorValue with + | null, discValueFn when obj.Equals(discValueFn, null) -> + // use __typename from filter and do type.ToSting() for values + ValueNone + | discExpr, discValueFn when obj.Equals(discValueFn, null) -> + // use discriminator and do type.ToSting() for values + ValueNone + | null, discValueFn -> + // use __typename from filter and execute discValueFn for values + ValueNone + | discExpr, discValueFn -> + // use discriminator and execute discValueFn for values + + let discriminatorValue = discValueFn t + let param = Expression.Parameter(typeof<'T>, "x") + let discExpr = getPropertyExpr<'T> param "__discriminator" // Assuming discriminator field name + let valueExpr = Expression.Constant(discriminatorValue) + ValueSome(Expression.Equal(discExpr, valueExpr)) + + // Create and execute the final expression + let param = Expression.Parameter(typeof<'T>, "x") + query.Provider.CreateQuery<'T>(buildFilterExpr<'T> param buildTypeDiscriminatorCheck filter query) + + member filter.Apply<'T, 'D>(query : IQueryable<'T>, + [] getDiscriminator : Expression> | null, + [] getDiscriminatorValue : Type -> 'D) = + + // Helper for discriminator comparison + let buildTypeDiscriminatorCheck (t: Type) = + match getDiscriminator, getDiscriminatorValue with + | null, discValueFn when obj.Equals(discValueFn, null) -> + // use __typename from filter and do type.ToSting() for values + ValueNone + | discExpr, discValueFn when obj.Equals(discValueFn, null) -> + // use discriminator and do type.ToSting() for values + ValueNone + | null, discValueFn -> + // use __typename from filter and execute discValueFn for values + ValueNone + | discExpr, discValueFn -> + // use discriminator and execute discValueFn for values + let discriminatorValue = discValueFn t + let param = Expression.Parameter(typeof<'T>, "x") + let discExpr = getPropertyExpr<'T> param "__discriminator" // Assuming discriminator field name + let valueExpr = Expression.Constant(discriminatorValue) + ValueSome(Expression.Equal(discExpr, valueExpr)) + + // Create and execute the final expression + let param = Expression.Parameter(typeof<'T>, "x") + query.Provider.CreateQuery<'T>(buildFilterExpr<'T> param buildTypeDiscriminatorCheck filter query) diff --git a/src/FSharp.Data.GraphQL.Shared/FSharp.Data.GraphQL.Shared.fsproj b/src/FSharp.Data.GraphQL.Shared/FSharp.Data.GraphQL.Shared.fsproj index 8d270a060..39291f9de 100644 --- a/src/FSharp.Data.GraphQL.Shared/FSharp.Data.GraphQL.Shared.fsproj +++ b/src/FSharp.Data.GraphQL.Shared/FSharp.Data.GraphQL.Shared.fsproj @@ -17,6 +17,9 @@ <_Parameter1>FSharp.Data.GraphQL.Server + + <_Parameter1>FSharp.Data.GraphQL.Server.Middleware + <_Parameter1>FSharp.Data.GraphQL.Client diff --git a/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs index 0b1ee1dee..cdd4a47b9 100644 --- a/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs @@ -9,6 +9,9 @@ open FSharp.Data.GraphQL open FSharp.Data.GraphQL.Types open FSharp.Data.GraphQL.Linq open FSharp.Data.GraphQL.Execution +open FSharp.Data.GraphQL.Server.Middleware +open FSharp.Data.GraphQL.Server.Middleware.ObjectListFilter +open FSharp.Data.GraphQL.Server.Middleware.ObjectListFilterExtensions type Contact = { Email : string } @@ -286,3 +289,129 @@ let ``LINQ interpreter works with orderByDesc arg``() = (7, "Jeneffer") (4, "Ben") ] +[] +let ``ObjectListFilter works with Equals operator``() = + let filter = Equals { FieldName = "firstName"; Value = "Jonathan" } // :> IComparable + let queryable = data.AsQueryable() + let filteredData = filter.Apply(queryable) |> Seq.toList + List.length filteredData |> equals 1 + let result = List.head filteredData + result.ID |> equals 2 + result.FirstName |> equals "Jonathan" + result.LastName |> equals "Abrams" + result.Contact |> equals { Email = "j.abrams@gmail.com" } + result.Friends |> equals [] + +[] +let ``ObjectListFilter works with GreaterThan operator``() = + let filter = GreaterThan { FieldName = "id"; Value = 4 } // :> IComparable + let queryable = data.AsQueryable() + let filteredData = filter.Apply(queryable) |> Seq.toList + List.length filteredData |> equals 1 + let result = List.head filteredData + result.ID |> equals 7 + result.FirstName |> equals "Jeneffer" + result.LastName |> equals "Trif" + result.Contact |> equals { Email = "j.trif@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" } ] + +[] +let ``ObjectListFilter works with LessThan operator``() = + let filter = LessThan { FieldName = "id"; Value = 4 } // :> IComparable + let queryable = data.AsQueryable() + let filteredData = filter.Apply(queryable) |> Seq.toList + List.length filteredData |> equals 1 + let result = List.head filteredData + result.ID |> equals 2 + result.FirstName |> equals "Jonathan" + result.LastName |> equals "Abrams" + result.Contact |> equals { Email = "j.abrams@gmail.com" } + result.Friends |> equals [] + +[] +let ``ObjectListFilter works with StartsWith operator``() = + let filter = StartsWith { FieldName = "firstName"; Value = "J" } + let queryable = data.AsQueryable() + let filteredData = filter.Apply(queryable) |> Seq.toList + List.length filteredData |> equals 2 + let result = List.head filteredData + result.ID |> equals 2 + result.FirstName |> equals "Jonathan" + result.LastName |> equals "Abrams" + result.Contact |> equals { Email = "j.abrams@gmail.com" } + result.Friends |> equals [] + +[] +let ``ObjectListFilter works with Contains operator``() = + let filter = Contains { FieldName = "firstName"; Value = "en" } + let queryable = data.AsQueryable() + let filteredData = filter.Apply(queryable) |> Seq.toList + List.length filteredData |> equals 2 + let result = List.head filteredData + result.ID |> equals 4 + result.FirstName |> equals "Ben" + result.LastName |> equals "Adams" + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + +[] +let ``ObjectListFilter works with EndsWith operator``() = + let filter = EndsWith { FieldName = "lastName"; Value = "ams" } + let queryable = data.AsQueryable() + let filteredData = filter.Apply(queryable) |> Seq.toList + List.length filteredData |> equals 2 + let result = List.head filteredData + result.ID |> equals 4 + result.FirstName |> equals "Ben" + result.LastName |> equals "Adams" + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + +[] +let ``ObjectListFilter works with AND operator``() = + let filter = + And ( + Contains { FieldName = "firstName"; Value = "en" }, + Equals { FieldName = "lastName"; Value = "Adams" } + ) + let queryable = data.AsQueryable() + let filteredData = filter.Apply(queryable) |> Seq.toList + List.length filteredData |> equals 1 + let result = List.head filteredData + result.ID |> equals 4 + result.FirstName |> equals "Ben" + result.LastName |> equals "Adams" + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + +//[] +//let ``ObjectListFilter works with OR operator``() = +// let filter = +// Or ( +// GreaterThan { FieldName = "id"; Value = 4 }, +// Equals { FieldName = "lastName"; Value = "Adams" } +// ) +// let queryable = data.AsQueryable() +// let filteredData = filter.Apply(queryable) |> Seq.toList +// List.length filteredData |> equals 2 +// let result = List.head filteredData +// result.ID |> equals 4 +// result.FirstName |> equals "Ben" +// result.LastName |> equals "Adams" +// result.Contact |> equals { Email = "b.adams@gmail.com" } +// result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + +//[] +//let ``ObjectListFilter works with FilterField operator``() = +// let filter = +// FilterField { FieldName = "Friends"; Value = Contains { FieldName = "Email"; Value = "l.trif@gmail.com" } } +// let queryable = data.AsQueryable() +// let filteredData = filter.Apply(queryable) |> Seq.toList +// List.length filteredData |> equals 1 +// let result = List.head filteredData +// result.ID |> equals 4 +// result.FirstName |> equals "Ben" +// result.LastName |> equals "Adams" +// result.Contact |> equals { Email = "b.adams@gmail.com" } +// result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + From 51ef7f88e60adec2732913b6ef5f00ed9d4e2af6 Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Mon, 10 Mar 2025 17:02:33 +0200 Subject: [PATCH 02/23] Refactored ObjectListFilter and implemented Or and Not cases tests --- .../ObjectListFilter.fs | 248 ++++++++---------- tests/FSharp.Data.GraphQL.Tests/LinqTests.fs | 46 ++-- 2 files changed, 142 insertions(+), 152 deletions(-) diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs index 472b60233..e386bcff8 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs @@ -4,12 +4,9 @@ open System open System.Linq open System.Linq.Expressions open System.Runtime.InteropServices -open Microsoft.FSharp.Quotations /// A filter definition for a field value. -type FieldFilter<'Val> = - { FieldName : string - Value : 'Val } +type FieldFilter<'Val> = { FieldName : string; Value : 'Val } /// A filter definition for an object list. type ObjectListFilter = @@ -82,146 +79,125 @@ module ObjectListFilterExtensions = // | t when Type.(=)(t, typeof) -> ResidentialPropertiesConstants.Discriminators.Building // ) - // Helper to create parameter expression for the lambda - let param<'T> = Expression.Parameter(typeof<'T>, "x") - - // Helper to get property value - let getPropertyExpr<'T> (param: ParameterExpression) fieldName = - Expression.PropertyOrField(param, fieldName) - // Helper to create lambda from body expression - let makeLambda<'T> (param: ParameterExpression) (body: Expression) = - let delegateType = typedefof>.MakeGenericType([|typeof<'T>; body.Type|]) - Expression.Lambda(delegateType, body, param) + let makeLambda<'T> (param : ParameterExpression) (body : Expression) = + let delegateType = typedefof>.MakeGenericType ([| typeof<'T>; body.Type |]) + Expression.Lambda (delegateType, body, param) + + let private genericWhereMethod = + typeof.GetMethods () + |> Seq.where (fun m -> m.Name = "Where") + |> Seq.find (fun m -> + let parameters = m.GetParameters () + parameters.Length = 2 + && parameters[1].ParameterType.GetGenericTypeDefinition () = typedefof>>) // Helper to create Where expression - let whereExpr<'T> (query : IQueryable<'T>) (param: ParameterExpression) predicate = - let whereMethod = - typeof.GetMethods() - |> Seq.where (fun m -> m.Name = "Where") - |> Seq.find (fun m -> - let parameters = m.GetParameters() - parameters.Length = 2 - && parameters[1].ParameterType.GetGenericTypeDefinition() = typedefof>>) - |> fun m -> m.MakeGenericMethod([|typeof<'T>|]) - Expression.Call(whereMethod, [|query.Expression; makeLambda<'T> param predicate|]) - - // Main filter logic - let rec buildFilterExpr<'T> (param: ParameterExpression) buildTypeDiscriminatorCheck filter (query : IQueryable<'T>) = - let buildFilterExpr = buildFilterExpr<'T> param buildTypeDiscriminatorCheck + let whereExpr<'T> (query : IQueryable<'T>) (param : ParameterExpression) predicate = + let whereMethod = genericWhereMethod.MakeGenericMethod ([| typeof<'T> |]) + Expression.Call (whereMethod, [| query.Expression; makeLambda<'T> param predicate |]) + + let private StringStartsWithMethod = typeof.GetMethod ("StartsWith", [| typeof |]) + let private StringEndsWithMethod = typeof.GetMethod ("EndsWith", [| typeof |]) + let private StringContainsMethod = typeof.GetMethod ("Contains", [| typeof |]) + + let getField (param : ParameterExpression) fieldName = Expression.PropertyOrField (param, fieldName) + + [] + type SourceExpression private (expression : Expression) = + new (parameter : ParameterExpression) = SourceExpression (parameter :> Expression) + new (``member`` : MemberExpression) = SourceExpression (``member`` :> Expression) + member _.Value = expression + static member op_Implicit (source : SourceExpression) = source.Value + static member op_Implicit (parameter : ParameterExpression) = SourceExpression (parameter :> Expression) + static member op_Implicit (``member`` : MemberExpression) = SourceExpression (``member`` :> Expression) + + let rec buildFilterExpr (param : SourceExpression) buildTypeDiscriminatorCheck filter : Expression = + let build = buildFilterExpr param buildTypeDiscriminatorCheck match filter with - | NoFilter -> query.Expression - | And (f1, f2) -> - let q1 = buildFilterExpr f1 query |> Expression.Lambda>> |> _.Compile().Invoke() - let q2 = buildFilterExpr f2 q1 |> Expression.Lambda>> |> _.Compile().Invoke() - q2.Expression - //| Or (f1, f2) -> - // let expr1 = buildFilterExpr f1 query - // let expr2 = buildFilterExpr f2 query - // Expression.OrElse(expr1, expr2) |> whereExpr<'T> query param :> Expression - //| Not f -> - // let exceptMethod = - // typeof.GetMethods() - // |> Array.find (fun m -> m.Name = "Except") - // |> fun m -> m.MakeGenericMethod([|typeof<'T>|]) - // Expression.Call(exceptMethod, [|query.Expression; buildFilterExpr f|]) - //| OfTypes types -> - // match types.Value with - // | [] -> query.Expression // No types specified, return original query - // | types -> - // let typeChecks = - // types - // |> List.vchoose buildTypeDiscriminatorCheck - // |> List.fold (fun acc expr -> - // match acc with - // | ValueNone -> ValueSome expr - // | ValueSome prevExpr -> ValueSome (Expression.OrElse(prevExpr, expr))) ValueNone - - // match typeChecks with - // | ValueNone -> query.Expression - // | ValueSome expr -> whereExpr query expr :> Expression - | Equals f -> - Expression.Equal(getPropertyExpr<'T> param f.FieldName, Expression.Constant(f.Value)) |> whereExpr<'T> query param :> Expression - | GreaterThan f -> - Expression.GreaterThan(getPropertyExpr<'T> param f.FieldName, Expression.Constant(f.Value)) |> whereExpr<'T> query param :> Expression - | LessThan f -> - Expression.LessThan(getPropertyExpr<'T> param f.FieldName, Expression.Constant(f.Value)) |> whereExpr<'T> query param :> Expression + | NoFilter -> Expression.Constant (true) + | Not f -> f |> build |> Expression.Not :> Expression + | And (f1, f2) -> Expression.AndAlso (build f1, build f2) + | Or (f1, f2) -> Expression.OrElse (build f1, build f2) + | Equals f -> Expression.Equal (Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) + | GreaterThan f -> Expression.GreaterThan (Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) + | LessThan f -> Expression.LessThan (Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) | StartsWith f -> - let methodInfo = typeof.GetMethod("StartsWith", [|typeof|]) - Expression.Call(getPropertyExpr<'T> param f.FieldName, methodInfo, Expression.Constant(f.Value)) |> whereExpr<'T> query param :> Expression + Expression.Call (Expression.PropertyOrField (param, f.FieldName), StringStartsWithMethod, Expression.Constant (f.Value)) | EndsWith f -> - let methodInfo = typeof.GetMethod("EndsWith", [|typeof|]) - Expression.Call(getPropertyExpr<'T> param f.FieldName, methodInfo, Expression.Constant(f.Value)) |> whereExpr<'T> query param :> Expression + Expression.Call (Expression.PropertyOrField (param, f.FieldName), StringEndsWithMethod, Expression.Constant (f.Value)) | Contains f -> - let methodInfo = typeof.GetMethod("Contains", [|typeof|]) - Expression.Call(getPropertyExpr<'T> param f.FieldName, methodInfo, Expression.Constant(f.Value)) |> whereExpr<'T> query param :> Expression + Expression.Call (Expression.PropertyOrField (param, f.FieldName), StringContainsMethod, Expression.Constant (f.Value)) + | OfTypes types -> + types.Value + |> Seq.map (fun t -> buildTypeDiscriminatorCheck param t) + |> Seq.reduce (fun acc expr -> Expression.Or (acc, expr)) | FilterField f -> - let propExpr = getPropertyExpr<'T> param f.FieldName - match propExpr.Type.GetInterfaces() - |> Array.tryFind (fun t -> - t.IsGenericType && t.GetGenericTypeDefinition() = typedefof>) with - | Some queryableType -> - let elementType = queryableType.GetGenericArguments().[0] - let subFilter = f.Value - let subQuery = Expression.Convert(propExpr, queryableType) - Expression.Call(typeof, "Any", [|elementType|], subQuery) |> whereExpr<'T> query param :> Expression - | None -> query.Expression + let paramExpr = Expression.PropertyOrField (param, f.FieldName) + buildFilterExpr (SourceExpression paramExpr) buildTypeDiscriminatorCheck f.Value type ObjectListFilter with - member filter.Apply<'T, 'D>(query : IQueryable<'T>, - compareDiscriminator : Expression>, - getDiscriminatorValue : (Type -> 'D)) = - - // Helper for discriminator comparison - let buildTypeDiscriminatorCheck (t: Type) = - match compareDiscriminator, getDiscriminatorValue with - | null, discValueFn when obj.Equals(discValueFn, null) -> - // use __typename from filter and do type.ToSting() for values - ValueNone - | discExpr, discValueFn when obj.Equals(discValueFn, null) -> - // use discriminator and do type.ToSting() for values - ValueNone - | null, discValueFn -> - // use __typename from filter and execute discValueFn for values - ValueNone - | discExpr, discValueFn -> - // use discriminator and execute discValueFn for values - - let discriminatorValue = discValueFn t - let param = Expression.Parameter(typeof<'T>, "x") - let discExpr = getPropertyExpr<'T> param "__discriminator" // Assuming discriminator field name - let valueExpr = Expression.Constant(discriminatorValue) - ValueSome(Expression.Equal(discExpr, valueExpr)) - - // Create and execute the final expression - let param = Expression.Parameter(typeof<'T>, "x") - query.Provider.CreateQuery<'T>(buildFilterExpr<'T> param buildTypeDiscriminatorCheck filter query) - - member filter.Apply<'T, 'D>(query : IQueryable<'T>, - [] getDiscriminator : Expression> | null, - [] getDiscriminatorValue : Type -> 'D) = - - // Helper for discriminator comparison - let buildTypeDiscriminatorCheck (t: Type) = - match getDiscriminator, getDiscriminatorValue with - | null, discValueFn when obj.Equals(discValueFn, null) -> - // use __typename from filter and do type.ToSting() for values - ValueNone - | discExpr, discValueFn when obj.Equals(discValueFn, null) -> - // use discriminator and do type.ToSting() for values - ValueNone - | null, discValueFn -> - // use __typename from filter and execute discValueFn for values - ValueNone - | discExpr, discValueFn -> - // use discriminator and execute discValueFn for values - let discriminatorValue = discValueFn t - let param = Expression.Parameter(typeof<'T>, "x") - let discExpr = getPropertyExpr<'T> param "__discriminator" // Assuming discriminator field name - let valueExpr = Expression.Constant(discriminatorValue) - ValueSome(Expression.Equal(discExpr, valueExpr)) - - // Create and execute the final expression - let param = Expression.Parameter(typeof<'T>, "x") - query.Provider.CreateQuery<'T>(buildFilterExpr<'T> param buildTypeDiscriminatorCheck filter query) + member filter.Apply<'T, 'D> + (query : IQueryable<'T>, compareDiscriminator : Expression>, getDiscriminatorValue : (Type -> 'D)) + = + + match filter with + | NoFilter -> query + | _ -> + + // Helper for discriminator comparison + let buildTypeDiscriminatorCheck (param : SourceExpression) (t : Type) = + match compareDiscriminator, getDiscriminatorValue with + | null, discValueFn when obj.Equals (discValueFn, null) -> + // use __typename from filter and do type.ToSting() for values + Unchecked.defaultof + | discExpr, discValueFn when obj.Equals (discValueFn, null) -> + // use discriminator and do type.ToSting() for values + Unchecked.defaultof + | null, discValueFn -> + // use __typename from filter and execute discValueFn for values + Unchecked.defaultof + | discExpr, discValueFn -> + // use discriminator and execute discValueFn for values + + let discriminatorValue = discValueFn t + Expression.Equal (Expression.PropertyOrField (param, "__discriminator"), Expression.Constant (discriminatorValue)) + + let queryExpr = + let param = Expression.Parameter (typeof<'T>, "x") + let body = buildFilterExpr (SourceExpression param) buildTypeDiscriminatorCheck filter + whereExpr<'T> query param body + // Create and execute the final expression + query.Provider.CreateQuery<'T> (queryExpr) + + member filter.Apply<'T, 'D> + (query : IQueryable<'T>, [] getDiscriminator : Expression> | null, [] getDiscriminatorValue : Type -> 'D) + = + + match filter with + | NoFilter -> query + | _ -> + // Helper for discriminator comparison + let buildTypeDiscriminatorCheck (param : SourceExpression) (t : Type) = + match getDiscriminator, getDiscriminatorValue with + | null, discValueFn when obj.Equals (discValueFn, null) -> + // use __typename from filter and do type.ToSting() for values + Unchecked.defaultof + | discExpr, discValueFn when obj.Equals (discValueFn, null) -> + // use discriminator and do type.ToSting() for values + Unchecked.defaultof + | null, discValueFn -> + // use __typename from filter and execute discValueFn for values + Unchecked.defaultof + | discExpr, discValueFn -> + // use discriminator and execute discValueFn for values + let discriminatorValue = discValueFn t + Expression.Equal (Expression.PropertyOrField (param, "__discriminator"), Expression.Constant (discriminatorValue)) + + let queryExpr = + let param = Expression.Parameter (typeof<'T>, "x") + let body = buildFilterExpr (SourceExpression param) buildTypeDiscriminatorCheck filter + whereExpr<'T> query param body + // Create and execute the final expression + query.Provider.CreateQuery<'T> (queryExpr) diff --git a/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs index cdd4a47b9..f6d9684fd 100644 --- a/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs @@ -384,22 +384,22 @@ let ``ObjectListFilter works with AND operator``() = result.Contact |> equals { Email = "b.adams@gmail.com" } result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] -//[] -//let ``ObjectListFilter works with OR operator``() = -// let filter = -// Or ( -// GreaterThan { FieldName = "id"; Value = 4 }, -// Equals { FieldName = "lastName"; Value = "Adams" } -// ) -// let queryable = data.AsQueryable() -// let filteredData = filter.Apply(queryable) |> Seq.toList -// List.length filteredData |> equals 2 -// let result = List.head filteredData -// result.ID |> equals 4 -// result.FirstName |> equals "Ben" -// result.LastName |> equals "Adams" -// result.Contact |> equals { Email = "b.adams@gmail.com" } -// result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] +[] +let ``ObjectListFilter works with OR operator``() = + let filter = + Or ( + GreaterThan { FieldName = "id"; Value = 4 }, + Equals { FieldName = "lastName"; Value = "Adams" } + ) + let queryable = data.AsQueryable() + let filteredData = filter.Apply(queryable) |> Seq.toList + List.length filteredData |> equals 2 + let result = List.head filteredData + result.ID |> equals 4 + result.FirstName |> equals "Ben" + result.LastName |> equals "Adams" + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] //[] //let ``ObjectListFilter works with FilterField operator``() = @@ -415,3 +415,17 @@ let ``ObjectListFilter works with AND operator``() = // result.Contact |> equals { Email = "b.adams@gmail.com" } // result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] +[] +let ``ObjectListFilter works with NOT operator``() = + let filter = + Not (Equals { FieldName = "lastName"; Value = "Adams" }) + let queryable = data.AsQueryable() + let filteredData = filter.Apply(queryable) |> Seq.toList + List.length filteredData |> equals 2 + let result1 = List.head filteredData + result1.ID |> equals 2 + result1.FirstName |> equals "Jonathan" + result1.LastName |> equals "Abrams" + result1.Contact |> equals { Email = "j.abrams@gmail.com" } + result1.Friends |> equals [] + From 0fa9d2a11ef83347686005d882ef3eb1b63c89ae Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Tue, 11 Mar 2025 14:18:45 +0200 Subject: [PATCH 03/23] Implemented new tests for different cases of buildTypeDiscriminatorCheck --- .../ObjectListFilter.fs | 53 ++++-- .../TypeSystemExtensions.fs | 2 +- tests/FSharp.Data.GraphQL.Tests/LinqTests.fs | 151 +++++++++++++++++- 3 files changed, 188 insertions(+), 18 deletions(-) diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs index e386bcff8..a05f50a6b 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs @@ -1,9 +1,6 @@ namespace FSharp.Data.GraphQL.Server.Middleware open System -open System.Linq -open System.Linq.Expressions -open System.Runtime.InteropServices /// A filter definition for a field value. type FieldFilter<'Val> = { FieldName : string; Value : 'Val } @@ -19,7 +16,7 @@ type ObjectListFilter = | StartsWith of FieldFilter | EndsWith of FieldFilter | Contains of FieldFilter - | OfTypes of FieldFilter + | OfTypes of Type list | FilterField of FieldFilter | NoFilter @@ -57,9 +54,15 @@ module ObjectListFilter = /// Creates a new ObjectListFilter representing a NOT opreation for the existing one. let ( !!! ) filter = Not filter +open System.Linq +open System.Linq.Expressions +open System.Runtime.InteropServices +open System.Reflection + [] module ObjectListFilterExtensions = + //// discriminator custom condition //let a () = // filter.Apply( @@ -79,6 +82,31 @@ module ObjectListFilterExtensions = // | t when Type.(=)(t, typeof) -> ResidentialPropertiesConstants.Discriminators.Building // ) + type DiscriminatorExpression<'T, 'D> = + | GetDiscriminatorValue of ('T -> 'D) + | CompareDiscriminator of Expression> + + [] + type ObjectListFilterLinqOptions<'T, 'D> ( + discriminatorExpression : DiscriminatorExpression<'T, 'D> | null, + [] getDiscriminatorValue: (Type -> 'D) | null, + [] serializeMemberName: (MemberInfo -> string) | null) = + + member _.DiscriminatorExpression = discriminatorExpression |> ValueOption.ofObj + member _.GetDiscriminatorValue = getDiscriminatorValue |> ValueOption.ofObj + member _.SerializeMemberName = serializeMemberName |> ValueOption.ofObj + + static member None = ObjectListFilterLinqOptions<'T, 'D> (null, null, null) + + new (getDiscriminatorValue : 'T -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (GetDiscriminatorValue getDiscriminatorValue, null, null) + new (compareDiscriminator : Expression>) = ObjectListFilterLinqOptions<'T, 'D> (CompareDiscriminator compareDiscriminator, null, null) + new (getDiscriminatorValue : Type -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (null, getDiscriminatorValue, null) + new (serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (null, null, serializeMemberName) + + new (getDiscriminatorValue : 'T -> 'D, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (GetDiscriminatorValue getDiscriminatorValue, null, serializeMemberName) + new (compareDiscriminator : Expression>, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (CompareDiscriminator compareDiscriminator, null, serializeMemberName) + new (getDiscriminatorValue : Type -> 'D, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (null, getDiscriminatorValue, serializeMemberName) + // Helper to create lambda from body expression let makeLambda<'T> (param : ParameterExpression) (body : Expression) = let delegateType = typedefof>.MakeGenericType ([| typeof<'T>; body.Type |]) @@ -129,7 +157,7 @@ module ObjectListFilterExtensions = | Contains f -> Expression.Call (Expression.PropertyOrField (param, f.FieldName), StringContainsMethod, Expression.Constant (f.Value)) | OfTypes types -> - types.Value + types |> Seq.map (fun t -> buildTypeDiscriminatorCheck param t) |> Seq.reduce (fun acc expr -> Expression.Or (acc, expr)) | FilterField f -> @@ -181,19 +209,22 @@ type ObjectListFilter with // Helper for discriminator comparison let buildTypeDiscriminatorCheck (param : SourceExpression) (t : Type) = match getDiscriminator, getDiscriminatorValue with - | null, discValueFn when obj.Equals (discValueFn, null) -> + | null, discValueFn when obj.Equals(discValueFn, null) -> // use __typename from filter and do type.ToSting() for values - Unchecked.defaultof - | discExpr, discValueFn when obj.Equals (discValueFn, null) -> + let typename = t.FullName + Expression.Equal(Expression.PropertyOrField(param, "__typename"), Expression.Constant(typename)) :> Expression + | discExpr, discValueFn when obj.Equals(discValueFn, null) -> // use discriminator and do type.ToSting() for values - Unchecked.defaultof + let typename = t.FullName + Expression.Equal(Expression.Invoke(discExpr, param), Expression.Constant(typename)) :> Expression | null, discValueFn -> // use __typename from filter and execute discValueFn for values - Unchecked.defaultof + let discriminatorValue = discValueFn t + Expression.Equal(Expression.PropertyOrField(param, "__typename"), Expression.Constant(discriminatorValue)) :> Expression | discExpr, discValueFn -> // use discriminator and execute discValueFn for values let discriminatorValue = discValueFn t - Expression.Equal (Expression.PropertyOrField (param, "__discriminator"), Expression.Constant (discriminatorValue)) + Expression.Equal (Expression.Invoke(discExpr, param), Expression.Constant (discriminatorValue)) let queryExpr = let param = Expression.Parameter (typeof<'T>, "x") diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/TypeSystemExtensions.fs b/src/FSharp.Data.GraphQL.Server.Middleware/TypeSystemExtensions.fs index 27fae4438..fae77fe24 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/TypeSystemExtensions.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/TypeSystemExtensions.fs @@ -40,7 +40,7 @@ module TypeSystemExtensions = | ValueNone -> raise (MalformedGQLQueryException ($"Type '{name}' not found in schema.")) match typeFields.Keys |> Seq.map getType |> Seq.toList with | [] -> ValueNone - | filters -> ValueSome (f &&& (OfTypes { FieldName = "__typename"; Value = filters })) + | filters -> ValueSome (f &&& (OfTypes filters)) | _ -> ValueSome f | false, _ -> ValueNone | true, _ -> raise (InvalidOperationException "Invalid filter argument type.") diff --git a/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs index f6d9684fd..8ab66fa34 100644 --- a/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs @@ -12,6 +12,7 @@ open FSharp.Data.GraphQL.Execution open FSharp.Data.GraphQL.Server.Middleware open FSharp.Data.GraphQL.Server.Middleware.ObjectListFilter open FSharp.Data.GraphQL.Server.Middleware.ObjectListFilterExtensions +open System.Linq.Expressions type Contact = { Email : string } @@ -54,6 +55,7 @@ let data = Contact = { Email = "j.trif@gmail.com" } Friends = [ { Email = "j.abrams@gmail.com" } ] } ] + let internal undefined<'t> = Unchecked.defaultof<'t> let resolveRoot ctx () = @@ -352,7 +354,7 @@ let ``ObjectListFilter works with Contains operator``() = result.FirstName |> equals "Ben" result.LastName |> equals "Adams" result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] [] let ``ObjectListFilter works with EndsWith operator``() = @@ -365,11 +367,11 @@ let ``ObjectListFilter works with EndsWith operator``() = result.FirstName |> equals "Ben" result.LastName |> equals "Adams" result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] [] let ``ObjectListFilter works with AND operator``() = - let filter = + let filter = And ( Contains { FieldName = "firstName"; Value = "en" }, Equals { FieldName = "lastName"; Value = "Adams" } @@ -386,7 +388,7 @@ let ``ObjectListFilter works with AND operator``() = [] let ``ObjectListFilter works with OR operator``() = - let filter = + let filter = Or ( GreaterThan { FieldName = "id"; Value = 4 }, Equals { FieldName = "lastName"; Value = "Adams" } @@ -403,7 +405,7 @@ let ``ObjectListFilter works with OR operator``() = //[] //let ``ObjectListFilter works with FilterField operator``() = -// let filter = +// let filter = // FilterField { FieldName = "Friends"; Value = Contains { FieldName = "Email"; Value = "l.trif@gmail.com" } } // let queryable = data.AsQueryable() // let filteredData = filter.Apply(queryable) |> Seq.toList @@ -417,7 +419,7 @@ let ``ObjectListFilter works with OR operator``() = [] let ``ObjectListFilter works with NOT operator``() = - let filter = + let filter = Not (Equals { FieldName = "lastName"; Value = "Adams" }) let queryable = data.AsQueryable() let filteredData = filter.Apply(queryable) |> Seq.toList @@ -429,3 +431,140 @@ let ``ObjectListFilter works with NOT operator``() = result1.Contact |> equals { Email = "j.abrams@gmail.com" } result1.Friends |> equals [] +type Complex = + { ID : int + Name : string + Discriminator : string } + +type Building = + { ID : int + Name : string + Discriminator : string } + +type Community = + { ID : int + Name : string + Discriminator : string + Complexes : int list + Buildings : int list } + +type Property = + | Complex of Complex + | Building of Building + | Community of Community + +[] +let ``ObjectListFilter works with getDiscriminator for Complex``() = + let propertyData: Property list = + [ + Complex { ID = 1; Name = "Complex A"; Discriminator = typeof.FullName} + Building { ID = 2; Name = "Building B"; Discriminator = typeof.FullName } + Community { ID = 3; Name = "Community C"; Discriminator = typeof.FullName; Complexes = [1]; Buildings = [2] } + Complex { ID = 4; Name = "Complex AA"; Discriminator = typeof.FullName } + Building { ID = 5; Name = "Building BB"; Discriminator = typeof.FullName } + Community { ID = 6; Name = "Community CC"; Discriminator = typeof.FullName; Complexes = [4]; Buildings = [5] } + ] + let queryable = propertyData.AsQueryable() + let filter = OfTypes [typeof] + let filteredData = + filter.Apply( + queryable, + getDiscriminator = + fun p -> + match p with + | Complex c -> c.Discriminator + | Building b -> b.Discriminator + | Community c -> c.Discriminator + ) + |> Seq.toList + List.length filteredData |> equals 2 + let result1 = List.head filteredData + match result1 with + | Complex c -> + c.ID |> equals 1 + c.Name |> equals "Complex A" + | _ -> failwith "Expected Complex" + let result2 = List.last filteredData + match result2 with + | Complex c -> + c.ID |> equals 4 + c.Name |> equals "Complex AA" + | _ -> failwith "Expected Complex" + + +[] +let ``ObjectListFilter works with getDiscriminator and getDiscriminatorValue for Complex``() = + let propertyData: Property list = + [ + Complex { ID = 1; Name = "Complex A"; Discriminator = "Complex" } + Building { ID = 2; Name = "Building B"; Discriminator = "Building" } + Community { ID = 3; Name = "Community C"; Discriminator = "Community"; Complexes = [1]; Buildings = [2] } + Complex { ID = 4; Name = "Complex AA"; Discriminator = "Complex" } + Building { ID = 5; Name = "Building BB"; Discriminator = "Building" } + Community { ID = 6; Name = "Community CC"; Discriminator = "Community"; Complexes = [4]; Buildings = [5] } + ] + let queryable = propertyData.AsQueryable() + let filter = OfTypes [typeof] + let filteredData = + filter.Apply( + queryable, + (fun p -> + match p with + | Complex c -> c.Discriminator + | Building b -> b.Discriminator + | Community c -> c.Discriminator), + (function + | t when t = typeof -> "Complex" + | t when t = typeof -> "Building" + | t when t = typeof -> "Community" + | _ -> raise (NotSupportedException "Type not supported")) + ) + |> Seq.toList + List.length filteredData |> equals 2 + let result1 = List.head filteredData + match result1 with + | Complex c -> + c.ID |> equals 1 + c.Name |> equals "Complex A" + | _ -> failwith "Expected Complex" + let result2 = List.last filteredData + match result2 with + | Complex c -> + c.ID |> equals 4 + c.Name |> equals "Complex AA" + | _ -> failwith "Expected Complex" + + + +[] +let ``ObjectListFilter works with getDiscriminatorValue for Complex``() = + let propertyData: Property list = + [ + Complex { ID = 1; Name = "Complex A"; Discriminator = typeof.FullName} + Building { ID = 2; Name = "Building B"; Discriminator = typeof.FullName } + Community { ID = 3; Name = "Community C"; Discriminator = typeof.FullName; Complexes = [1]; Buildings = [2] } + Complex { ID = 4; Name = "Complex AA"; Discriminator = typeof.FullName } + Building { ID = 5; Name = "Building BB"; Discriminator = typeof.FullName } + Community { ID = 6; Name = "Community CC"; Discriminator = typeof.FullName; Complexes = [4]; Buildings = [5] } + ] + let queryable = propertyData.AsQueryable() + let filter = OfTypes [typeof] + let filteredData = + filter.Apply( + queryable, + getDiscriminatorValue = (fun t -> t.FullName) + ) + |> Seq.toList + List.length filteredData |> equals 2 + let result1 = List.head filteredData + match result1 with + | Complex c -> + c.ID |> equals 1 + c.Name |> equals "Complex A" + | _ -> failwith "Expected Complex" + let result2 = List.last filteredData + match result2 with + | Complex c -> + c.ID |> equals 4 + c.Name |> equals "Complex AA" + | _ -> failwith "Expected Complex" From 6fe1ed12805b8ff6fb51277a686084cd113a8e60 Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Wed, 12 Mar 2025 13:20:49 +0200 Subject: [PATCH 04/23] Implemented tests for different discriminator cases --- .../ObjectListFilter.fs | 104 ++++++----------- .../TypeSystemExtensions.fs | 28 +++-- tests/FSharp.Data.GraphQL.Tests/LinqTests.fs | 109 +++++++++--------- 3 files changed, 108 insertions(+), 133 deletions(-) diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs index a05f50a6b..0920bcb5c 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs @@ -82,36 +82,6 @@ module ObjectListFilterExtensions = // | t when Type.(=)(t, typeof) -> ResidentialPropertiesConstants.Discriminators.Building // ) - type DiscriminatorExpression<'T, 'D> = - | GetDiscriminatorValue of ('T -> 'D) - | CompareDiscriminator of Expression> - - [] - type ObjectListFilterLinqOptions<'T, 'D> ( - discriminatorExpression : DiscriminatorExpression<'T, 'D> | null, - [] getDiscriminatorValue: (Type -> 'D) | null, - [] serializeMemberName: (MemberInfo -> string) | null) = - - member _.DiscriminatorExpression = discriminatorExpression |> ValueOption.ofObj - member _.GetDiscriminatorValue = getDiscriminatorValue |> ValueOption.ofObj - member _.SerializeMemberName = serializeMemberName |> ValueOption.ofObj - - static member None = ObjectListFilterLinqOptions<'T, 'D> (null, null, null) - - new (getDiscriminatorValue : 'T -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (GetDiscriminatorValue getDiscriminatorValue, null, null) - new (compareDiscriminator : Expression>) = ObjectListFilterLinqOptions<'T, 'D> (CompareDiscriminator compareDiscriminator, null, null) - new (getDiscriminatorValue : Type -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (null, getDiscriminatorValue, null) - new (serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (null, null, serializeMemberName) - - new (getDiscriminatorValue : 'T -> 'D, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (GetDiscriminatorValue getDiscriminatorValue, null, serializeMemberName) - new (compareDiscriminator : Expression>, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (CompareDiscriminator compareDiscriminator, null, serializeMemberName) - new (getDiscriminatorValue : Type -> 'D, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (null, getDiscriminatorValue, serializeMemberName) - - // Helper to create lambda from body expression - let makeLambda<'T> (param : ParameterExpression) (body : Expression) = - let delegateType = typedefof>.MakeGenericType ([| typeof<'T>; body.Type |]) - Expression.Lambda (delegateType, body, param) - let private genericWhereMethod = typeof.GetMethods () |> Seq.where (fun m -> m.Name = "Where") @@ -123,7 +93,7 @@ module ObjectListFilterExtensions = // Helper to create Where expression let whereExpr<'T> (query : IQueryable<'T>) (param : ParameterExpression) predicate = let whereMethod = genericWhereMethod.MakeGenericMethod ([| typeof<'T> |]) - Expression.Call (whereMethod, [| query.Expression; makeLambda<'T> param predicate |]) + Expression.Call (whereMethod, [| query.Expression; Expression.Lambda> (predicate, param) |]) let private StringStartsWithMethod = typeof.GetMethod ("StartsWith", [| typeof |]) let private StringEndsWithMethod = typeof.GetMethod ("EndsWith", [| typeof |]) @@ -164,67 +134,61 @@ module ObjectListFilterExtensions = let paramExpr = Expression.PropertyOrField (param, f.FieldName) buildFilterExpr (SourceExpression paramExpr) buildTypeDiscriminatorCheck f.Value -type ObjectListFilter with + [] + type ObjectListFilterLinqOptions<'T, 'D> ( + [] compareDiscriminator : Expression> | null, + [] getDiscriminatorValue : (Type -> 'D) | null, + [] serializeMemberName : (MemberInfo -> string) | null) = - member filter.Apply<'T, 'D> - (query : IQueryable<'T>, compareDiscriminator : Expression>, getDiscriminatorValue : (Type -> 'D)) - = + member _.CompareDiscriminator = compareDiscriminator |> ValueOption.ofObj + member _.GetDiscriminatorValue = getDiscriminatorValue |> ValueOption.ofObj + //member _.SerializeMemberName = serializeMemberName |> ValueOption.ofObj - match filter with - | NoFilter -> query - | _ -> + static member None = ObjectListFilterLinqOptions<'T, 'D> (null, null, null) - // Helper for discriminator comparison - let buildTypeDiscriminatorCheck (param : SourceExpression) (t : Type) = - match compareDiscriminator, getDiscriminatorValue with - | null, discValueFn when obj.Equals (discValueFn, null) -> - // use __typename from filter and do type.ToSting() for values - Unchecked.defaultof - | discExpr, discValueFn when obj.Equals (discValueFn, null) -> - // use discriminator and do type.ToSting() for values - Unchecked.defaultof - | null, discValueFn -> - // use __typename from filter and execute discValueFn for values - Unchecked.defaultof - | discExpr, discValueFn -> - // use discriminator and execute discValueFn for values + static member GetCompareDiscriminator (getDiscriminatorValue : Expression>) = + let tParam = Expression.Parameter (typeof<'T>, "x") + let dParam = Expression.Parameter (typeof<'D>, "d") + let body = Expression.Equal(Expression.Invoke(getDiscriminatorValue, tParam), dParam) + Expression.Lambda> (body, tParam, dParam) - let discriminatorValue = discValueFn t - Expression.Equal (Expression.PropertyOrField (param, "__discriminator"), Expression.Constant (discriminatorValue)) + new (getDiscriminator : Expression>) = ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, null, null) + new (compareDiscriminator : Expression>) = ObjectListFilterLinqOptions<'T, 'D> (compareDiscriminator, null, null) + new (getDiscriminatorValue : Type -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (null, getDiscriminatorValue, null) + //new (serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (null, null, serializeMemberName) - let queryExpr = - let param = Expression.Parameter (typeof<'T>, "x") - let body = buildFilterExpr (SourceExpression param) buildTypeDiscriminatorCheck filter - whereExpr<'T> query param body - // Create and execute the final expression - query.Provider.CreateQuery<'T> (queryExpr) + new (getDiscriminator : Expression>, getDiscriminatorValue : Type -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, getDiscriminatorValue, null) + + //new (getDiscriminator : Expression>, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, null, serializeMemberName) + //new (compareDiscriminator : Expression>, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (compareDiscriminator, null, serializeMemberName) + //new (getDiscriminatorValue : Type -> 'D, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (null, getDiscriminatorValue, serializeMemberName) + +type ObjectListFilter with - member filter.Apply<'T, 'D> - (query : IQueryable<'T>, [] getDiscriminator : Expression> | null, [] getDiscriminatorValue : Type -> 'D) - = + member filter.Apply<'T, 'D> (query : IQueryable<'T>, [] options : ObjectListFilterLinqOptions<'T, 'D>) = match filter with | NoFilter -> query | _ -> // Helper for discriminator comparison let buildTypeDiscriminatorCheck (param : SourceExpression) (t : Type) = - match getDiscriminator, getDiscriminatorValue with - | null, discValueFn when obj.Equals(discValueFn, null) -> + match options.CompareDiscriminator, options.GetDiscriminatorValue with + | ValueNone, ValueNone -> // use __typename from filter and do type.ToSting() for values let typename = t.FullName Expression.Equal(Expression.PropertyOrField(param, "__typename"), Expression.Constant(typename)) :> Expression - | discExpr, discValueFn when obj.Equals(discValueFn, null) -> + | ValueSome discExpr, ValueNone -> // use discriminator and do type.ToSting() for values let typename = t.FullName - Expression.Equal(Expression.Invoke(discExpr, param), Expression.Constant(typename)) :> Expression - | null, discValueFn -> + Expression.Invoke(discExpr, param, Expression.Constant(typename)) :> Expression + | ValueNone, ValueSome discValueFn -> // use __typename from filter and execute discValueFn for values let discriminatorValue = discValueFn t Expression.Equal(Expression.PropertyOrField(param, "__typename"), Expression.Constant(discriminatorValue)) :> Expression - | discExpr, discValueFn -> + | ValueSome discExpr, ValueSome discValueFn -> // use discriminator and execute discValueFn for values let discriminatorValue = discValueFn t - Expression.Equal (Expression.Invoke(discExpr, param), Expression.Constant (discriminatorValue)) + Expression.Invoke(discExpr, param, Expression.Constant (discriminatorValue)) let queryExpr = let param = Expression.Parameter (typeof<'T>, "x") diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/TypeSystemExtensions.fs b/src/FSharp.Data.GraphQL.Server.Middleware/TypeSystemExtensions.fs index fae77fe24..209966af6 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/TypeSystemExtensions.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/TypeSystemExtensions.fs @@ -23,6 +23,21 @@ module TypeSystemExtensions = open ObjectListFilter.Operators + type ExecutionInfo with + + member this.ResolveAbstractionFilter (typeMap : TypeMap) = + match this.Kind with + | ResolveAbstraction typeFields -> + let getType name = + match typeMap.TryFind name with + | ValueSome tdef -> tdef.Type + | ValueNone -> raise (MalformedGQLQueryException ($"Type '{name}' not found in schema.")) + match typeFields.Keys |> Seq.map getType |> Seq.toList with + | [] -> ValueNone + | filters -> ValueSome (OfTypes filters) + | _ -> ValueNone + + type ResolveFieldContext with /// @@ -32,16 +47,9 @@ module TypeSystemExtensions = member this.Filter = match this.Args.TryGetValue "filter" with | true, (:? ObjectListFilter as f) -> - match this.ExecutionInfo.Kind with - | ResolveAbstraction typeFields -> - let getType name = - match this.Context.Schema.TypeMap.TryFind name with - | ValueSome tdef -> tdef.Type - | ValueNone -> raise (MalformedGQLQueryException ($"Type '{name}' not found in schema.")) - match typeFields.Keys |> Seq.map getType |> Seq.toList with - | [] -> ValueNone - | filters -> ValueSome (f &&& (OfTypes filters)) - | _ -> ValueSome f + match this.ExecutionInfo.ResolveAbstractionFilter (this.Context.Schema.TypeMap) with + | ValueSome ofTypes -> ValueSome (ofTypes &&& f) + | ValueNone -> ValueSome f | false, _ -> ValueNone | true, _ -> raise (InvalidOperationException "Invalid filter argument type.") diff --git a/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs index 8ab66fa34..829f31192 100644 --- a/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs @@ -453,11 +453,12 @@ type Property = | Building of Building | Community of Community + [] let ``ObjectListFilter works with getDiscriminator for Complex``() = let propertyData: Property list = [ - Complex { ID = 1; Name = "Complex A"; Discriminator = typeof.FullName} + Complex { ID = 1; Name = "Complex A"; Discriminator = typeof.FullName } Building { ID = 2; Name = "Building B"; Discriminator = typeof.FullName } Community { ID = 3; Name = "Community C"; Discriminator = typeof.FullName; Complexes = [1]; Buildings = [2] } Complex { ID = 4; Name = "Complex AA"; Discriminator = typeof.FullName } @@ -466,17 +467,13 @@ let ``ObjectListFilter works with getDiscriminator for Complex``() = ] let queryable = propertyData.AsQueryable() let filter = OfTypes [typeof] - let filteredData = - filter.Apply( - queryable, - getDiscriminator = - fun p -> - match p with - | Complex c -> c.Discriminator - | Building b -> b.Discriminator - | Community c -> c.Discriminator - ) - |> Seq.toList + let options = + ObjectListFilterLinqOptions( + (function + | Complex c -> c.Discriminator + | Building b -> b.Discriminator + | Community c -> c.Discriminator)) + let filteredData = filter.Apply(queryable,options) |> Seq.toList List.length filteredData |> equals 2 let result1 = List.head filteredData match result1 with @@ -496,30 +493,28 @@ let ``ObjectListFilter works with getDiscriminator for Complex``() = let ``ObjectListFilter works with getDiscriminator and getDiscriminatorValue for Complex``() = let propertyData: Property list = [ - Complex { ID = 1; Name = "Complex A"; Discriminator = "Complex" } - Building { ID = 2; Name = "Building B"; Discriminator = "Building" } - Community { ID = 3; Name = "Community C"; Discriminator = "Community"; Complexes = [1]; Buildings = [2] } - Complex { ID = 4; Name = "Complex AA"; Discriminator = "Complex" } - Building { ID = 5; Name = "Building BB"; Discriminator = "Building" } - Community { ID = 6; Name = "Community CC"; Discriminator = "Community"; Complexes = [4]; Buildings = [5] } + Complex { ID = 1; Name = "Complex A"; Discriminator = typeof.Name} + Building { ID = 2; Name = "Building B"; Discriminator = typeof.Name } + Community { ID = 3; Name = "Community C"; Discriminator = typeof.Name; Complexes = [1]; Buildings = [2] } + Complex { ID = 4; Name = "Complex AA"; Discriminator = typeof.Name } + Building { ID = 5; Name = "Building BB"; Discriminator = typeof.Name } + Community { ID = 6; Name = "Community CC"; Discriminator = typeof.Name; Complexes = [4]; Buildings = [5] } ] let queryable = propertyData.AsQueryable() let filter = OfTypes [typeof] - let filteredData = - filter.Apply( - queryable, - (fun p -> - match p with - | Complex c -> c.Discriminator - | Building b -> b.Discriminator - | Community c -> c.Discriminator), + let options = + ObjectListFilterLinqOptions( + (function + | Complex c -> c.Discriminator + | Building b -> b.Discriminator + | Community c -> c.Discriminator), (function | t when t = typeof -> "Complex" | t when t = typeof -> "Building" | t when t = typeof -> "Community" | _ -> raise (NotSupportedException "Type not supported")) ) - |> Seq.toList + let filteredData = filter.Apply(queryable,options) |> Seq.toList List.length filteredData |> equals 2 let result1 = List.head filteredData match result1 with @@ -534,37 +529,45 @@ let ``ObjectListFilter works with getDiscriminator and getDiscriminatorValue for c.Name |> equals "Complex AA" | _ -> failwith "Expected Complex" +type Cow = + { ID : int + Name : string + __typename : string } +type Horse = + { ID : int + Name : string + __typename : string } + +let animalData = + [ + { ID = 1; Name = "Cow A"; __typename = typeof.Name } + { ID = 2; Name = "Horse B"; __typename = typeof.Name } + { ID = 3; Name = "Cow C"; __typename = typeof.Name } + { ID = 4; Name = "Horse D"; __typename = typeof.Name } + ] [] -let ``ObjectListFilter works with getDiscriminatorValue for Complex``() = - let propertyData: Property list = - [ - Complex { ID = 1; Name = "Complex A"; Discriminator = typeof.FullName} - Building { ID = 2; Name = "Building B"; Discriminator = typeof.FullName } - Community { ID = 3; Name = "Community C"; Discriminator = typeof.FullName; Complexes = [1]; Buildings = [2] } - Complex { ID = 4; Name = "Complex AA"; Discriminator = typeof.FullName } - Building { ID = 5; Name = "Building BB"; Discriminator = typeof.FullName } - Community { ID = 6; Name = "Community CC"; Discriminator = typeof.FullName; Complexes = [4]; Buildings = [5] } - ] - let queryable = propertyData.AsQueryable() - let filter = OfTypes [typeof] - let filteredData = - filter.Apply( - queryable, - getDiscriminatorValue = (fun t -> t.FullName) - ) - |> Seq.toList +let ``ObjectListFilter works with getDiscriminatorValue for Horse``() = + let queryable = animalData.AsQueryable() + let filter = OfTypes [typeof] + let options = + ObjectListFilterLinqOptions( + getDiscriminatorValue = (function + | t when t = typeof -> t.Name + | t when t = typeof -> t.Name + | _ -> raise (NotSupportedException "Type not supported")) + ) + let filteredData = filter.Apply(queryable, options) |> Seq.toList List.length filteredData |> equals 2 let result1 = List.head filteredData match result1 with - | Complex c -> - c.ID |> equals 1 - c.Name |> equals "Complex A" - | _ -> failwith "Expected Complex" + | h -> + h.ID |> equals 2 + h.Name |> equals "Horse B" + | _ -> failwith "Expected Horse" let result2 = List.last filteredData match result2 with - | Complex c -> - c.ID |> equals 4 - c.Name |> equals "Complex AA" - | _ -> failwith "Expected Complex" + | h -> + h.ID |> equals 4 + h.Name |> equals "Horse D" From 506110c84b1a2bab9bc11c7ed404a002c2a1b4c6 Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Wed, 12 Mar 2025 14:36:13 +0200 Subject: [PATCH 05/23] Remove unmatching rule --- tests/FSharp.Data.GraphQL.Tests/LinqTests.fs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs index 829f31192..51dd2df35 100644 --- a/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs @@ -565,7 +565,6 @@ let ``ObjectListFilter works with getDiscriminatorValue for Horse``() = | h -> h.ID |> equals 2 h.Name |> equals "Horse B" - | _ -> failwith "Expected Horse" let result2 = List.last filteredData match result2 with | h -> From 3c0dc436369eee94dce70cc35f97c8b15d9324e2 Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Fri, 14 Mar 2025 14:44:22 +0200 Subject: [PATCH 06/23] Splitted LinqTests and ObjectListFilterLinqTests into separated files --- .../TypeSystemExtensions.fs | 1 - .../FSharp.Data.GraphQL.Tests.fsproj | 3 +- tests/FSharp.Data.GraphQL.Tests/LinqTests.fs | 572 ------------------ .../ObjectListFilterLinqTests.fs | 363 +++++++++++ .../SelectLinqTests.fs | 288 +++++++++ 5 files changed, 653 insertions(+), 574 deletions(-) delete mode 100644 tests/FSharp.Data.GraphQL.Tests/LinqTests.fs create mode 100644 tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs create mode 100644 tests/FSharp.Data.GraphQL.Tests/SelectLinqTests.fs diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/TypeSystemExtensions.fs b/src/FSharp.Data.GraphQL.Server.Middleware/TypeSystemExtensions.fs index 209966af6..fdf3554da 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/TypeSystemExtensions.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/TypeSystemExtensions.fs @@ -37,7 +37,6 @@ module TypeSystemExtensions = | filters -> ValueSome (OfTypes filters) | _ -> ValueNone - type ResolveFieldContext with /// diff --git a/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj b/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj index 10ef9d6ca..f4352578b 100644 --- a/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj +++ b/tests/FSharp.Data.GraphQL.Tests/FSharp.Data.GraphQL.Tests.fsproj @@ -63,7 +63,8 @@ - + + diff --git a/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs deleted file mode 100644 index 51dd2df35..000000000 --- a/tests/FSharp.Data.GraphQL.Tests/LinqTests.fs +++ /dev/null @@ -1,572 +0,0 @@ -// The MIT License (MIT) -// Copyright (c) 2016 Bazinga Technologies Inc -module FSharp.Data.GraphQL.Tests.LinqTests - -open Xunit -open System -open System.Linq -open FSharp.Data.GraphQL -open FSharp.Data.GraphQL.Types -open FSharp.Data.GraphQL.Linq -open FSharp.Data.GraphQL.Execution -open FSharp.Data.GraphQL.Server.Middleware -open FSharp.Data.GraphQL.Server.Middleware.ObjectListFilter -open FSharp.Data.GraphQL.Server.Middleware.ObjectListFilterExtensions -open System.Linq.Expressions - -type Contact = - { Email : string } - -type Person = - { ID : int - FirstName : string - LastName : string - Contact : Contact - Friends : Contact list } - -let Contact = Define.Object("Contact", [ Define.Field("email", StringType, fun _ x -> x.Email) ]) - -let Person = - Define.Object("Person", - [ Define.Field("id", IDType, fun _ x -> string x.ID) - Define.AutoField("firstName", StringType) - Define.Field("lastName", StringType, fun _ x -> x.LastName) - Define.Field("fullName", StringType, fun _ x -> x.FirstName + " " + x.LastName) - Define.Field("contact", Contact, fun _ x -> x.Contact) - Define.Field("email", StringType, fun _ x -> x.Contact.Email) - Define.Field("friends", ListOf Contact, fun _ x -> x.Friends) ]) - -let data = - [ { ID = 4 - FirstName = "Ben" - LastName = "Adams" - Contact = { Email = "b.adams@gmail.com" } - Friends = - [ { Email = "j.abrams@gmail.com" } - { Email = "l.trif@gmail.com" } ] } - { ID = 2 - FirstName = "Jonathan" - LastName = "Abrams" - Contact = { Email = "j.abrams@gmail.com" } - Friends = [] } - { ID = 7 - FirstName = "Jeneffer" - LastName = "Trif" - Contact = { Email = "j.trif@gmail.com" } - Friends = [ { Email = "j.abrams@gmail.com" } ] } ] - - -let internal undefined<'t> = Unchecked.defaultof<'t> - -let resolveRoot ctx () = - let info = ctx.ExecutionInfo - let queryable = data.AsQueryable() - let result = queryable.Apply(info) |> Seq.toList - result - -let linqArgs = - [ Define.Input("id", Nullable IDType) - Define.Input("skip", Nullable IntType) - Define.Input("take", Nullable IntType) - Define.Input("orderBy", Nullable StringType) - Define.Input("orderByDesc", Nullable StringType) - Define.Input("first", Nullable IntType) - Define.Input("last", Nullable IntType) - Define.Input("before", Nullable StringType) - Define.Input("after", Nullable StringType) ] - -let schema = - Schema(Define.Object("RootQuery", - [ Define.Field("people", ListOf Person, "", linqArgs, - fun ctx () -> - let info = ctx.ExecutionInfo - let queryable = data.AsQueryable() - let result = queryable.Apply(info) |> Seq.toList - result) ])) - -let schemaProcessor = Executor(schema) - -[] -let ``LINQ interpreter works with auto-fields``() = - let plan = schemaProcessor.CreateExecutionPlanOrFail """ - query Example { - people { - firstName - } - } - """ - let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList - List.length people |> equals 3 - let result = List.head people - result.FirstName |> equals "Ben" - result.LastName |> equals undefined - result.Contact |> equals undefined - result.Friends |> equals undefined - -[] -let ``LINQ interpreter works with fields with defined resolvers``() = - let plan = schemaProcessor.CreateExecutionPlanOrFail """ - query Example { - people { - lastName - } - } - """ - let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList - List.length people |> equals 3 - let result = List.head people - result.FirstName |> equals undefined - result.LastName |> equals "Adams" - result.Contact |> equals undefined - result.Friends |> equals undefined - -[] -let ``LINQ interpreter works with fields referring to nested property resolver``() = - let plan = schemaProcessor.CreateExecutionPlanOrFail """ - query Example { - people { - contact { email } - } - } - """ - let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList - List.length people |> equals 3 - let result = List.head people - result.FirstName |> equals undefined - result.LastName |> equals undefined - result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals undefined - -[] -let ``LINQ interpreter works with nested collections``() = - let plan = schemaProcessor.CreateExecutionPlanOrFail """ - query Example { - people { - friends { email } - } - } - """ - let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList - List.length people |> equals 3 - let result = List.head people - result.FirstName |> equals undefined - result.LastName |> equals undefined - result.Contact |> equals undefined - result.Friends |> equals [ { Email = "j.abrams@gmail.com" } - { Email = "l.trif@gmail.com" } ] - -[] -let ``LINQ interpreter works with nested property getters in resolve function``() = - let plan = schemaProcessor.CreateExecutionPlanOrFail """ - query Example { - people { - email - } - } - """ - let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList - List.length people |> equals 3 - let result = List.head people - result.FirstName |> equals undefined - result.LastName |> equals undefined - // this should be resolved, because email resolver is: fun _ x -> x.Contact.Email - result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals undefined - -[] -let ``LINQ interpreter resolves multiple properties from complex resolvers``() = - let plan = schemaProcessor.CreateExecutionPlanOrFail """ - query Example { - people { - fullName - } - } - """ - let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList - List.length people |> equals 3 - let result = List.head people - // both FirstName and LastName should be resolved, because - // they are accessed from within fullName function resolver - result.FirstName |> equals "Ben" - result.LastName |> equals "Adams" - result.Contact |> equals undefined - result.Friends |> equals undefined - -[] -let ``LINQ interpreter works with id arg``() = - let plan = schemaProcessor.CreateExecutionPlanOrFail """ - query Example { - people(id: 2) { - id - firstName - } - } - """ - let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList - List.length people |> equals 1 - let result = List.head people - result.ID |> equals 2 - result.FirstName |> equals "Jonathan" - result.LastName |> equals undefined - result.Contact |> equals undefined - result.Friends |> equals undefined - -[] -let ``LINQ interpreter works with skip arg``() = - let plan = schemaProcessor.CreateExecutionPlanOrFail """ - query Example { - people(skip: 2) { - id - firstName - } - } - """ - let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList - List.length people |> equals 1 - let result = List.head people - result.ID |> equals 7 - result.FirstName |> equals "Jeneffer" - result.LastName |> equals undefined - result.Contact |> equals undefined - result.Friends |> equals undefined - -[] -let ``LINQ interpreter works with take arg``() = - let plan = schemaProcessor.CreateExecutionPlanOrFail """ - query Example { - people(take: 2) { - id - firstName - } - } - """ - let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList - List.length people |> equals 2 - let result = people |> List.map (fun p -> (p.ID, p.FirstName)) - result |> equals [ (4, "Ben") - (2, "Jonathan") ] - -[] -let ``LINQ interpreter works with orderBy arg``() = - let plan = schemaProcessor.CreateExecutionPlanOrFail """ - query Example { - people(orderBy: "firstName") { - id - firstName - } - } - """ - let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList - List.length people |> equals 3 - let result = people |> List.map (fun p -> (p.ID, p.FirstName)) - result |> equals [ (4, "Ben") - (7, "Jeneffer") - (2, "Jonathan") ] - -[] -let ``LINQ interpreter works with orderByDesc arg``() = - let plan = schemaProcessor.CreateExecutionPlanOrFail """ - query Example { - people(orderByDesc: "firstName") { - id - firstName - } - } - """ - let info = plan.["people"] - let people = data.AsQueryable().Apply(info) |> Seq.toList - List.length people |> equals 3 - let result = people |> List.map (fun p -> (p.ID, p.FirstName)) - result |> equals [ (2, "Jonathan") - (7, "Jeneffer") - (4, "Ben") ] - -[] -let ``ObjectListFilter works with Equals operator``() = - let filter = Equals { FieldName = "firstName"; Value = "Jonathan" } // :> IComparable - let queryable = data.AsQueryable() - let filteredData = filter.Apply(queryable) |> Seq.toList - List.length filteredData |> equals 1 - let result = List.head filteredData - result.ID |> equals 2 - result.FirstName |> equals "Jonathan" - result.LastName |> equals "Abrams" - result.Contact |> equals { Email = "j.abrams@gmail.com" } - result.Friends |> equals [] - -[] -let ``ObjectListFilter works with GreaterThan operator``() = - let filter = GreaterThan { FieldName = "id"; Value = 4 } // :> IComparable - let queryable = data.AsQueryable() - let filteredData = filter.Apply(queryable) |> Seq.toList - List.length filteredData |> equals 1 - let result = List.head filteredData - result.ID |> equals 7 - result.FirstName |> equals "Jeneffer" - result.LastName |> equals "Trif" - result.Contact |> equals { Email = "j.trif@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" } ] - -[] -let ``ObjectListFilter works with LessThan operator``() = - let filter = LessThan { FieldName = "id"; Value = 4 } // :> IComparable - let queryable = data.AsQueryable() - let filteredData = filter.Apply(queryable) |> Seq.toList - List.length filteredData |> equals 1 - let result = List.head filteredData - result.ID |> equals 2 - result.FirstName |> equals "Jonathan" - result.LastName |> equals "Abrams" - result.Contact |> equals { Email = "j.abrams@gmail.com" } - result.Friends |> equals [] - -[] -let ``ObjectListFilter works with StartsWith operator``() = - let filter = StartsWith { FieldName = "firstName"; Value = "J" } - let queryable = data.AsQueryable() - let filteredData = filter.Apply(queryable) |> Seq.toList - List.length filteredData |> equals 2 - let result = List.head filteredData - result.ID |> equals 2 - result.FirstName |> equals "Jonathan" - result.LastName |> equals "Abrams" - result.Contact |> equals { Email = "j.abrams@gmail.com" } - result.Friends |> equals [] - -[] -let ``ObjectListFilter works with Contains operator``() = - let filter = Contains { FieldName = "firstName"; Value = "en" } - let queryable = data.AsQueryable() - let filteredData = filter.Apply(queryable) |> Seq.toList - List.length filteredData |> equals 2 - let result = List.head filteredData - result.ID |> equals 4 - result.FirstName |> equals "Ben" - result.LastName |> equals "Adams" - result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] - -[] -let ``ObjectListFilter works with EndsWith operator``() = - let filter = EndsWith { FieldName = "lastName"; Value = "ams" } - let queryable = data.AsQueryable() - let filteredData = filter.Apply(queryable) |> Seq.toList - List.length filteredData |> equals 2 - let result = List.head filteredData - result.ID |> equals 4 - result.FirstName |> equals "Ben" - result.LastName |> equals "Adams" - result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] - -[] -let ``ObjectListFilter works with AND operator``() = - let filter = - And ( - Contains { FieldName = "firstName"; Value = "en" }, - Equals { FieldName = "lastName"; Value = "Adams" } - ) - let queryable = data.AsQueryable() - let filteredData = filter.Apply(queryable) |> Seq.toList - List.length filteredData |> equals 1 - let result = List.head filteredData - result.ID |> equals 4 - result.FirstName |> equals "Ben" - result.LastName |> equals "Adams" - result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] - -[] -let ``ObjectListFilter works with OR operator``() = - let filter = - Or ( - GreaterThan { FieldName = "id"; Value = 4 }, - Equals { FieldName = "lastName"; Value = "Adams" } - ) - let queryable = data.AsQueryable() - let filteredData = filter.Apply(queryable) |> Seq.toList - List.length filteredData |> equals 2 - let result = List.head filteredData - result.ID |> equals 4 - result.FirstName |> equals "Ben" - result.LastName |> equals "Adams" - result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] - -//[] -//let ``ObjectListFilter works with FilterField operator``() = -// let filter = -// FilterField { FieldName = "Friends"; Value = Contains { FieldName = "Email"; Value = "l.trif@gmail.com" } } -// let queryable = data.AsQueryable() -// let filteredData = filter.Apply(queryable) |> Seq.toList -// List.length filteredData |> equals 1 -// let result = List.head filteredData -// result.ID |> equals 4 -// result.FirstName |> equals "Ben" -// result.LastName |> equals "Adams" -// result.Contact |> equals { Email = "b.adams@gmail.com" } -// result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] - -[] -let ``ObjectListFilter works with NOT operator``() = - let filter = - Not (Equals { FieldName = "lastName"; Value = "Adams" }) - let queryable = data.AsQueryable() - let filteredData = filter.Apply(queryable) |> Seq.toList - List.length filteredData |> equals 2 - let result1 = List.head filteredData - result1.ID |> equals 2 - result1.FirstName |> equals "Jonathan" - result1.LastName |> equals "Abrams" - result1.Contact |> equals { Email = "j.abrams@gmail.com" } - result1.Friends |> equals [] - -type Complex = - { ID : int - Name : string - Discriminator : string } - -type Building = - { ID : int - Name : string - Discriminator : string } - -type Community = - { ID : int - Name : string - Discriminator : string - Complexes : int list - Buildings : int list } - -type Property = - | Complex of Complex - | Building of Building - | Community of Community - - -[] -let ``ObjectListFilter works with getDiscriminator for Complex``() = - let propertyData: Property list = - [ - Complex { ID = 1; Name = "Complex A"; Discriminator = typeof.FullName } - Building { ID = 2; Name = "Building B"; Discriminator = typeof.FullName } - Community { ID = 3; Name = "Community C"; Discriminator = typeof.FullName; Complexes = [1]; Buildings = [2] } - Complex { ID = 4; Name = "Complex AA"; Discriminator = typeof.FullName } - Building { ID = 5; Name = "Building BB"; Discriminator = typeof.FullName } - Community { ID = 6; Name = "Community CC"; Discriminator = typeof.FullName; Complexes = [4]; Buildings = [5] } - ] - let queryable = propertyData.AsQueryable() - let filter = OfTypes [typeof] - let options = - ObjectListFilterLinqOptions( - (function - | Complex c -> c.Discriminator - | Building b -> b.Discriminator - | Community c -> c.Discriminator)) - let filteredData = filter.Apply(queryable,options) |> Seq.toList - List.length filteredData |> equals 2 - let result1 = List.head filteredData - match result1 with - | Complex c -> - c.ID |> equals 1 - c.Name |> equals "Complex A" - | _ -> failwith "Expected Complex" - let result2 = List.last filteredData - match result2 with - | Complex c -> - c.ID |> equals 4 - c.Name |> equals "Complex AA" - | _ -> failwith "Expected Complex" - - -[] -let ``ObjectListFilter works with getDiscriminator and getDiscriminatorValue for Complex``() = - let propertyData: Property list = - [ - Complex { ID = 1; Name = "Complex A"; Discriminator = typeof.Name} - Building { ID = 2; Name = "Building B"; Discriminator = typeof.Name } - Community { ID = 3; Name = "Community C"; Discriminator = typeof.Name; Complexes = [1]; Buildings = [2] } - Complex { ID = 4; Name = "Complex AA"; Discriminator = typeof.Name } - Building { ID = 5; Name = "Building BB"; Discriminator = typeof.Name } - Community { ID = 6; Name = "Community CC"; Discriminator = typeof.Name; Complexes = [4]; Buildings = [5] } - ] - let queryable = propertyData.AsQueryable() - let filter = OfTypes [typeof] - let options = - ObjectListFilterLinqOptions( - (function - | Complex c -> c.Discriminator - | Building b -> b.Discriminator - | Community c -> c.Discriminator), - (function - | t when t = typeof -> "Complex" - | t when t = typeof -> "Building" - | t when t = typeof -> "Community" - | _ -> raise (NotSupportedException "Type not supported")) - ) - let filteredData = filter.Apply(queryable,options) |> Seq.toList - List.length filteredData |> equals 2 - let result1 = List.head filteredData - match result1 with - | Complex c -> - c.ID |> equals 1 - c.Name |> equals "Complex A" - | _ -> failwith "Expected Complex" - let result2 = List.last filteredData - match result2 with - | Complex c -> - c.ID |> equals 4 - c.Name |> equals "Complex AA" - | _ -> failwith "Expected Complex" - -type Cow = - { ID : int - Name : string - __typename : string } - -type Horse = - { ID : int - Name : string - __typename : string } - -let animalData = - [ - { ID = 1; Name = "Cow A"; __typename = typeof.Name } - { ID = 2; Name = "Horse B"; __typename = typeof.Name } - { ID = 3; Name = "Cow C"; __typename = typeof.Name } - { ID = 4; Name = "Horse D"; __typename = typeof.Name } - ] - -[] -let ``ObjectListFilter works with getDiscriminatorValue for Horse``() = - let queryable = animalData.AsQueryable() - let filter = OfTypes [typeof] - let options = - ObjectListFilterLinqOptions( - getDiscriminatorValue = (function - | t when t = typeof -> t.Name - | t when t = typeof -> t.Name - | _ -> raise (NotSupportedException "Type not supported")) - ) - let filteredData = filter.Apply(queryable, options) |> Seq.toList - List.length filteredData |> equals 2 - let result1 = List.head filteredData - match result1 with - | h -> - h.ID |> equals 2 - h.Name |> equals "Horse B" - let result2 = List.last filteredData - match result2 with - | h -> - h.ID |> equals 4 - h.Name |> equals "Horse D" diff --git a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs new file mode 100644 index 000000000..06c1f157f --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs @@ -0,0 +1,363 @@ +module FSharp.Data.GraphQL.Tests.ObjectListFilterLinqTests + +open Xunit +open System +open System.Linq +open FSharp.Data.GraphQL.Types +open FSharp.Data.GraphQL.Server.Middleware +open FSharp.Data.GraphQL.Tests.LinqTests + +[] +let ``ObjectListFilter works with Equals operator``() = + let filter = Equals { FieldName = "firstName"; Value = "Jonathan" } // :> IComparable + let queryable = data.AsQueryable() + let filteredData = queryable.Apply(filter) |> Seq.toList + List.length filteredData |> equals 1 + let result = List.head filteredData + result.ID |> equals 2 + result.FirstName |> equals "Jonathan" + result.LastName |> equals "Abrams" + result.Contact |> equals { Email = "j.abrams@gmail.com" } + result.Friends |> equals [] + +[] +let ``ObjectListFilter works with GreaterThan operator``() = + let filter = GreaterThan { FieldName = "id"; Value = 4 } // :> IComparable + let queryable = data.AsQueryable() + let filteredData = queryable.Apply(filter) |> Seq.toList + List.length filteredData |> equals 1 + let result = List.head filteredData + result.ID |> equals 7 + result.FirstName |> equals "Jeneffer" + result.LastName |> equals "Trif" + result.Contact |> equals { Email = "j.trif@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" } ] + +[] +let ``ObjectListFilter works with LessThan operator``() = + let filter = LessThan { FieldName = "id"; Value = 4 } // :> IComparable + let queryable = data.AsQueryable() + let filteredData = queryable.Apply(filter) |> Seq.toList + List.length filteredData |> equals 1 + let result = List.head filteredData + result.ID |> equals 2 + result.FirstName |> equals "Jonathan" + result.LastName |> equals "Abrams" + result.Contact |> equals { Email = "j.abrams@gmail.com" } + result.Friends |> equals [] + +[] +let ``ObjectListFilter works with StartsWith operator``() = + let filter = StartsWith { FieldName = "firstName"; Value = "J" } + let queryable = data.AsQueryable() + let filteredData = queryable.Apply(filter) |> Seq.toList + List.length filteredData |> equals 2 + let result = List.head filteredData + result.ID |> equals 2 + result.FirstName |> equals "Jonathan" + result.LastName |> equals "Abrams" + result.Contact |> equals { Email = "j.abrams@gmail.com" } + result.Friends |> equals [] + +[] +let ``ObjectListFilter works with Contains operator``() = + let filter = Contains { FieldName = "firstName"; Value = "en" } + let queryable = data.AsQueryable() + let filteredData = queryable.Apply(filter) |> Seq.toList + List.length filteredData |> equals 2 + let result = List.head filteredData + result.ID |> equals 4 + result.FirstName |> equals "Ben" + result.LastName |> equals "Adams" + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + +[] +let ``ObjectListFilter works with EndsWith operator``() = + let filter = EndsWith { FieldName = "lastName"; Value = "ams" } + let queryable = data.AsQueryable() + let filteredData = queryable.Apply(filter) |> Seq.toList + List.length filteredData |> equals 2 + let result = List.head filteredData + result.ID |> equals 4 + result.FirstName |> equals "Ben" + result.LastName |> equals "Adams" + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + +[] +let ``ObjectListFilter works with AND operator``() = + let filter = + And ( + Contains { FieldName = "firstName"; Value = "en" }, + Equals { FieldName = "lastName"; Value = "Adams" } + ) + let queryable = data.AsQueryable() + let filteredData = queryable.Apply(filter) |> Seq.toList + List.length filteredData |> equals 1 + let result = List.head filteredData + result.ID |> equals 4 + result.FirstName |> equals "Ben" + result.LastName |> equals "Adams" + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + +[] +let ``ObjectListFilter works with OR operator``() = + let filter = + Or ( + GreaterThan { FieldName = "id"; Value = 4 }, + Equals { FieldName = "lastName"; Value = "Adams" } + ) + let queryable = data.AsQueryable() + let filteredData = queryable.Apply(filter) |> Seq.toList + List.length filteredData |> equals 2 + let result = List.head filteredData + result.ID |> equals 4 + result.FirstName |> equals "Ben" + result.LastName |> equals "Adams" + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + +[] +let ``ObjectListFilter works with FilterField operator``() = + let filter = + FilterField { FieldName = "Friends"; Value = Contains { FieldName = "Email"; Value = "l.trif@gmail.com" } } + let queryable = data.AsQueryable() + let filteredData = queryable.Apply(filter) |> Seq.toList + List.length filteredData |> equals 1 + let result = List.head filteredData + result.ID |> equals 4 + result.FirstName |> equals "Ben" + result.LastName |> equals "Adams" + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + +[] +let ``ObjectListFilter works with NOT operator``() = + let filter = + Not (Equals { FieldName = "lastName"; Value = "Adams" }) + let queryable = data.AsQueryable() + let filteredData = queryable.Apply(filter) |> Seq.toList + List.length filteredData |> equals 2 + let result1 = List.head filteredData + result1.ID |> equals 2 + result1.FirstName |> equals "Jonathan" + result1.LastName |> equals "Abrams" + result1.Contact |> equals { Email = "j.abrams@gmail.com" } + result1.Friends |> equals [] + +type Complex = + { ID : int + Name : string + Discriminator : string } + +type Building = + { ID : int + Name : string + Discriminator : string } + +type Community = + { ID : int + Name : string + Discriminator : string + Complexes : int list + Buildings : int list } + +type Property = + | Complex of Complex + | Building of Building + | Community of Community + + +[] +let ``ObjectListFilter works with getDiscriminator for Complex``() = + let propertyData: Property list = + [ + Complex { ID = 1; Name = "Complex A"; Discriminator = typeof.FullName } + Building { ID = 2; Name = "Building B"; Discriminator = typeof.FullName } + Community { ID = 3; Name = "Community C"; Discriminator = typeof.FullName; Complexes = [1]; Buildings = [2] } + Complex { ID = 4; Name = "Complex AA"; Discriminator = typeof.FullName } + Building { ID = 5; Name = "Building BB"; Discriminator = typeof.FullName } + Community { ID = 6; Name = "Community CC"; Discriminator = typeof.FullName; Complexes = [4]; Buildings = [5] } + ] + let queryable = propertyData.AsQueryable() + let filter = OfTypes [typeof] + let options = + ObjectListFilterLinqOptions( + (function + | Complex c -> c.Discriminator + | Building b -> b.Discriminator + | Community c -> c.Discriminator)) + let filteredData = queryable.Apply(filter, options) |> Seq.toList + List.length filteredData |> equals 2 + let result1 = List.head filteredData + match result1 with + | Complex c -> + c.ID |> equals 1 + c.Name |> equals "Complex A" + | _ -> failwith "Expected Complex" + let result2 = List.last filteredData + match result2 with + | Complex c -> + c.ID |> equals 4 + c.Name |> equals "Complex AA" + | _ -> failwith "Expected Complex" + + +[] +let ``ObjectListFilter works with getDiscriminator and getDiscriminatorValue for Complex``() = + let propertyData: Property list = + [ + Complex { ID = 1; Name = "Complex A"; Discriminator = typeof.Name} + Building { ID = 2; Name = "Building B"; Discriminator = typeof.Name } + Community { ID = 3; Name = "Community C"; Discriminator = typeof.Name; Complexes = [1]; Buildings = [2] } + Complex { ID = 4; Name = "Complex AA"; Discriminator = typeof.Name } + Building { ID = 5; Name = "Building BB"; Discriminator = typeof.Name } + Community { ID = 6; Name = "Community CC"; Discriminator = typeof.Name; Complexes = [4]; Buildings = [5] } + ] + let queryable = propertyData.AsQueryable() + let filter = OfTypes [typeof] + let options = + ObjectListFilterLinqOptions( + (function + | Complex c -> c.Discriminator + | Building b -> b.Discriminator + | Community c -> c.Discriminator), + (function + | t when t = typeof -> "Complex" + | t when t = typeof -> "Building" + | t when t = typeof -> "Community" + | _ -> raise (NotSupportedException "Type not supported")) + ) + let filteredData = queryable.Apply(filter, options) |> Seq.toList + List.length filteredData |> equals 2 + let result1 = List.head filteredData + match result1 with + | Complex c -> + c.ID |> equals 1 + c.Name |> equals "Complex A" + | _ -> failwith "Expected Complex" + let result2 = List.last filteredData + match result2 with + | Complex c -> + c.ID |> equals 4 + c.Name |> equals "Complex AA" + | _ -> failwith "Expected Complex" + +type Cow = + { ID : int + Name : string + Discriminator : string + __typename : string } + +type Horse = + { ID : int + Name : string + Discriminator : string + __typename : string } + +type Hamster = + { ID : int + Name : string + Discriminator : string + __typename : string } + +let animalData = + [ + { ID = 1; Discriminator="Cow"; Name = "Cow A"; __typename = typeof.Name } + { ID = 2; Discriminator="Horse"; Name = "Horse B"; __typename = typeof.Name } + { ID = 3; Discriminator="Cow"; Name = "Cow C"; __typename = typeof.Name } + { ID = 4; Discriminator="Horse"; Name = "Horse D"; __typename = typeof.Name } + { ID = 5; Discriminator="Hamster"; Name = "Hamster E"; __typename = typeof.Name } + ] + +[] +let ``ObjectListFilter works with getDiscriminatorValue for Horse``() = + let queryable = animalData.AsQueryable() + let filter = OfTypes [typeof] + let options = + ObjectListFilterLinqOptions( + getDiscriminatorValue = (function + | t when t = typeof -> t.Name + | t when t = typeof -> t.Name + | _ -> raise (NotSupportedException "Type not supported")) + ) + let filteredData = queryable.Apply(filter, options) |> Seq.toList + List.length filteredData |> equals 2 + let result1 = List.head filteredData + match result1 with + | h -> + h.ID |> equals 2 + h.Name |> equals "Horse B" + let result2 = List.last filteredData + match result2 with + | h -> + h.ID |> equals 4 + h.Name |> equals "Horse D" + +[] +let ``ObjectListFilter works with getDiscriminatorValue startsWith for Horse and Hamster``() = + + let queryable = animalData.AsQueryable() + let filter = StartsWith { FieldName = "Discriminator"; Value = "H" } + let options = + ObjectListFilterLinqOptions ( + (fun entity (discriminator: string) -> + entity.Discriminator.StartsWith discriminator), + getDiscriminatorValue = (function + | t when t = typeof -> t.Name + | t when t = typeof -> t.Name + | t when t = typeof -> t.Name + | _ -> raise (NotSupportedException "Type not supported")) + ) + let filteredData = queryable.Apply(filter, options) |> Seq.toList + List.length filteredData |> equals 3 + let result1 = List.head filteredData + match result1 with + | c -> + c.ID |> equals 2 + c.Name |> equals "Horse B" + let result2 = List.last filteredData + match result2 with + | c -> + c.ID |> equals 5 + c.Name |> equals "Hamster E" + +type Product = { + Name : string + Tags : string list +} + +let productList = + [ + { Name = "Product A"; Tags = ["Tag1"; "Tag2"] } + { Name = "Product B"; Tags = ["Tag2"; "Tag3"] } + { Name = "Product C"; Tags = ["Tag3"; "Tag4"] } + { Name = "Product D"; Tags = ["Tag4"; "Tag5"] } + ] +let productArray = productList.ToArray() + +[] +let ``ObjectListFilter works with Contains operator collection type properties``() = + let queryable = productList.AsQueryable() + let filter = Contains { FieldName = "Tags"; Value = "Tag3" } + let filteredData = queryable.Apply(filter) |> Seq.toList + List.length filteredData |> equals 2 + let result1 = List.head filteredData + result1.Name |> equals "Product B" + let result2 = List.last filteredData + result2.Name |> equals "Product C" + + +[] +let ``ObjectListFilter works with Contains operator collection type properties with array``() = + let queryable = productArray.AsQueryable() + let filter = Contains { FieldName = "Tags"; Value = "Tag3" } + let filteredData = queryable.Apply(filter) |> Seq.toList + List.length filteredData |> equals 2 + let result1 = List.head filteredData + result1.Name |> equals "Product B" + let result2 = List.last filteredData + result2.Name |> equals "Product C" + diff --git a/tests/FSharp.Data.GraphQL.Tests/SelectLinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/SelectLinqTests.fs new file mode 100644 index 000000000..4b47580fa --- /dev/null +++ b/tests/FSharp.Data.GraphQL.Tests/SelectLinqTests.fs @@ -0,0 +1,288 @@ +// The MIT License (MIT) +// Copyright (c) 2016 Bazinga Technologies Inc +module FSharp.Data.GraphQL.Tests.LinqTests + +open Xunit +open System +open System.Linq +open FSharp.Data.GraphQL +open FSharp.Data.GraphQL.Linq +open FSharp.Data.GraphQL.Types + +type Contact = + { Email : string } + +type Person = + { ID : int + FirstName : string + LastName : string + Contact : Contact + Friends : Contact list } + +let Contact = Define.Object("Contact", [ Define.Field("email", StringType, fun _ x -> x.Email) ]) + +let Person = + Define.Object("Person", + [ Define.Field("id", IDType, fun _ x -> string x.ID) + Define.AutoField("firstName", StringType) + Define.Field("lastName", StringType, fun _ x -> x.LastName) + Define.Field("fullName", StringType, fun _ x -> x.FirstName + " " + x.LastName) + Define.Field("contact", Contact, fun _ x -> x.Contact) + Define.Field("email", StringType, fun _ x -> x.Contact.Email) + Define.Field("friends", ListOf Contact, fun _ x -> x.Friends) ]) + +let data = + [ { ID = 4 + FirstName = "Ben" + LastName = "Adams" + Contact = { Email = "b.adams@gmail.com" } + Friends = + [ { Email = "j.abrams@gmail.com" } + { Email = "l.trif@gmail.com" } ] } + { ID = 2 + FirstName = "Jonathan" + LastName = "Abrams" + Contact = { Email = "j.abrams@gmail.com" } + Friends = [] } + { ID = 7 + FirstName = "Jeneffer" + LastName = "Trif" + Contact = { Email = "j.trif@gmail.com" } + Friends = [ { Email = "j.abrams@gmail.com" } ] } ] + + +let internal undefined<'t> = Unchecked.defaultof<'t> + +let resolveRoot ctx () = + let info = ctx.ExecutionInfo + let queryable = data.AsQueryable() + let result = queryable.Apply(info) |> Seq.toList + result + +let linqArgs = + [ Define.Input("id", Nullable IDType) + Define.Input("skip", Nullable IntType) + Define.Input("take", Nullable IntType) + Define.Input("orderBy", Nullable StringType) + Define.Input("orderByDesc", Nullable StringType) + Define.Input("first", Nullable IntType) + Define.Input("last", Nullable IntType) + Define.Input("before", Nullable StringType) + Define.Input("after", Nullable StringType) ] + +let schema = + Schema(Define.Object("RootQuery", + [ Define.Field("people", ListOf Person, "", linqArgs, + fun ctx () -> + let info = ctx.ExecutionInfo + let queryable = data.AsQueryable() + let result = queryable.Apply(info) |> Seq.toList + result) ])) + +let schemaProcessor = Executor(schema) + +[] +let ``LINQ interpreter works with auto-fields``() = + let plan = schemaProcessor.CreateExecutionPlanOrFail """ + query Example { + people { + firstName + } + } + """ + let info = plan.["people"] + let people = data.AsQueryable().Apply(info) |> Seq.toList + List.length people |> equals 3 + let result = List.head people + result.FirstName |> equals "Ben" + result.LastName |> equals undefined + result.Contact |> equals undefined + result.Friends |> equals undefined + +[] +let ``LINQ interpreter works with fields with defined resolvers``() = + let plan = schemaProcessor.CreateExecutionPlanOrFail """ + query Example { + people { + lastName + } + } + """ + let info = plan.["people"] + let people = data.AsQueryable().Apply(info) |> Seq.toList + List.length people |> equals 3 + let result = List.head people + result.FirstName |> equals undefined + result.LastName |> equals "Adams" + result.Contact |> equals undefined + result.Friends |> equals undefined + +[] +let ``LINQ interpreter works with fields referring to nested property resolver``() = + let plan = schemaProcessor.CreateExecutionPlanOrFail """ + query Example { + people { + contact { email } + } + } + """ + let info = plan.["people"] + let people = data.AsQueryable().Apply(info) |> Seq.toList + List.length people |> equals 3 + let result = List.head people + result.FirstName |> equals undefined + result.LastName |> equals undefined + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals undefined + +[] +let ``LINQ interpreter works with nested collections``() = + let plan = schemaProcessor.CreateExecutionPlanOrFail """ + query Example { + people { + friends { email } + } + } + """ + let info = plan.["people"] + let people = data.AsQueryable().Apply(info) |> Seq.toList + List.length people |> equals 3 + let result = List.head people + result.FirstName |> equals undefined + result.LastName |> equals undefined + result.Contact |> equals undefined + result.Friends |> equals [ { Email = "j.abrams@gmail.com" } + { Email = "l.trif@gmail.com" } ] + +[] +let ``LINQ interpreter works with nested property getters in resolve function``() = + let plan = schemaProcessor.CreateExecutionPlanOrFail """ + query Example { + people { + email + } + } + """ + let info = plan.["people"] + let people = data.AsQueryable().Apply(info) |> Seq.toList + List.length people |> equals 3 + let result = List.head people + result.FirstName |> equals undefined + result.LastName |> equals undefined + // this should be resolved, because email resolver is: fun _ x -> x.Contact.Email + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals undefined + +[] +let ``LINQ interpreter resolves multiple properties from complex resolvers``() = + let plan = schemaProcessor.CreateExecutionPlanOrFail """ + query Example { + people { + fullName + } + } + """ + let info = plan.["people"] + let people = data.AsQueryable().Apply(info) |> Seq.toList + List.length people |> equals 3 + let result = List.head people + // both FirstName and LastName should be resolved, because + // they are accessed from within fullName function resolver + result.FirstName |> equals "Ben" + result.LastName |> equals "Adams" + result.Contact |> equals undefined + result.Friends |> equals undefined + +[] +let ``LINQ interpreter works with id arg``() = + let plan = schemaProcessor.CreateExecutionPlanOrFail """ + query Example { + people(id: 2) { + id + firstName + } + } + """ + let info = plan.["people"] + let people = data.AsQueryable().Apply(info) |> Seq.toList + List.length people |> equals 1 + let result = List.head people + result.ID |> equals 2 + result.FirstName |> equals "Jonathan" + result.LastName |> equals undefined + result.Contact |> equals undefined + result.Friends |> equals undefined + +[] +let ``LINQ interpreter works with skip arg``() = + let plan = schemaProcessor.CreateExecutionPlanOrFail """ + query Example { + people(skip: 2) { + id + firstName + } + } + """ + let info = plan.["people"] + let people = data.AsQueryable().Apply(info) |> Seq.toList + List.length people |> equals 1 + let result = List.head people + result.ID |> equals 7 + result.FirstName |> equals "Jeneffer" + result.LastName |> equals undefined + result.Contact |> equals undefined + result.Friends |> equals undefined + +[] +let ``LINQ interpreter works with take arg``() = + let plan = schemaProcessor.CreateExecutionPlanOrFail """ + query Example { + people(take: 2) { + id + firstName + } + } + """ + let info = plan.["people"] + let people = data.AsQueryable().Apply(info) |> Seq.toList + List.length people |> equals 2 + let result = people |> List.map (fun p -> (p.ID, p.FirstName)) + result |> equals [ (4, "Ben") + (2, "Jonathan") ] + +[] +let ``LINQ interpreter works with orderBy arg``() = + let plan = schemaProcessor.CreateExecutionPlanOrFail """ + query Example { + people(orderBy: "firstName") { + id + firstName + } + } + """ + let info = plan.["people"] + let people = data.AsQueryable().Apply(info) |> Seq.toList + List.length people |> equals 3 + let result = people |> List.map (fun p -> (p.ID, p.FirstName)) + result |> equals [ (4, "Ben") + (7, "Jeneffer") + (2, "Jonathan") ] + +[] +let ``LINQ interpreter works with orderByDesc arg``() = + let plan = schemaProcessor.CreateExecutionPlanOrFail """ + query Example { + people(orderByDesc: "firstName") { + id + firstName + } + } + """ + let info = plan.["people"] + let people = data.AsQueryable().Apply(info) |> Seq.toList + List.length people |> equals 3 + let result = people |> List.map (fun p -> (p.ID, p.FirstName)) + result |> equals [ (2, "Jonathan") + (7, "Jeneffer") + (4, "Ben") ] + From c9dd83ea43e0a26f14f8f42f19d49ef82686885c Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Fri, 14 Mar 2025 14:47:34 +0200 Subject: [PATCH 07/23] Implemented logic for different MemberInfo types in ObjectListFilter `Contains` case --- .../ObjectListFilter.fs | 171 +++++++++++------- 1 file changed, 109 insertions(+), 62 deletions(-) diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs index 0920bcb5c..7f1a6b54a 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs @@ -20,6 +20,75 @@ type ObjectListFilter = | FilterField of FieldFilter | NoFilter +open System.Linq +open System.Linq.Expressions +open System.Runtime.InteropServices +open System.Reflection + +/// +/// Represents a value that can be either None or Some. +/// + +/// +/// Allows to specify discriminator comparison or discriminator getter +/// and a function that return discriminator value depending on entity type +/// +/// +/// // discriminator custom condition +/// let result () = +/// queryable.Apply( +/// filter, +/// ObjectListFilterLinqOptions ( +/// (fun entity discriminator -> entity.Discriminator.StartsWith discriminator), +/// (function +/// | t when Type.(=)(t, typeof) -> "cat+v1" +/// | t when Type.(=)(t, typeof) -> "dog+v1") +/// ) +/// ) +/// +/// +/// // discriminator equals +/// let result () = +/// queryable.Apply( +/// filter, +/// ObjectListFilterLinqOptions ( +/// (fun entity -> entity.Discriminator), +/// (function +/// | t when Type.(=)(t, typeof) -> "cat" +/// | t when Type.(=)(t, typeof) -> "dog") +/// ) +/// ) +/// +[] +type ObjectListFilterLinqOptions<'T, 'D> ( + [] compareDiscriminator : Expression> | null, + [] getDiscriminatorValue : (Type -> 'D) | null, + [] serializeMemberName : (MemberInfo -> string) | null +) = + + member _.CompareDiscriminator = compareDiscriminator |> ValueOption.ofObj + member _.GetDiscriminatorValue = getDiscriminatorValue |> ValueOption.ofObj + //member _.SerializeMemberName = serializeMemberName |> ValueOption.ofObj + + static member None = ObjectListFilterLinqOptions<'T, 'D> (null, null, null) + + static member GetCompareDiscriminator (getDiscriminatorValue : Expression>) = + let tParam = Expression.Parameter (typeof<'T>, "x") + let dParam = Expression.Parameter (typeof<'D>, "d") + let body = Expression.Equal(Expression.Invoke(getDiscriminatorValue, tParam), dParam) + Expression.Lambda> (body, tParam, dParam) + + new (getDiscriminator : Expression>) = ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, null, null) + new (compareDiscriminator : Expression>) = ObjectListFilterLinqOptions<'T, 'D> (compareDiscriminator, null, null) + new (getDiscriminatorValue : Type -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (null, getDiscriminatorValue, null) + //new (serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (null, null, serializeMemberName) + + new (getDiscriminator : Expression>, getDiscriminatorValue : Type -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, getDiscriminatorValue, null) + + //new (getDiscriminator : Expression>, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, null, serializeMemberName) + //new (compareDiscriminator : Expression>, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (compareDiscriminator, null, serializeMemberName) + //new (getDiscriminatorValue : Type -> 'D, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (null, getDiscriminatorValue, serializeMemberName) + /// Contains tooling for working with ObjectListFilter. module ObjectListFilter = /// Contains operators for building and comparing ObjectListFilter values. @@ -54,34 +123,6 @@ module ObjectListFilter = /// Creates a new ObjectListFilter representing a NOT opreation for the existing one. let ( !!! ) filter = Not filter -open System.Linq -open System.Linq.Expressions -open System.Runtime.InteropServices -open System.Reflection - -[] -module ObjectListFilterExtensions = - - - //// discriminator custom condition - //let a () = - // filter.Apply( - // queryable, - // <@ fun e d -> e.Discriminator.StartsWith d @>, - // function - // | t when Type.(=)(t, typeof) -> ResidentialPropertiesConstants.Discriminators.Complex - // | t when Type.(=)(t, typeof) -> ResidentialPropertiesConstants.Discriminators.Building - // ) - //// discriminator equals - //let b () = - // filter.Apply( - // queryable, - // <@ fun e -> e.Discriminator @>, - // function - // | t when Type.(=)(t, typeof) -> ResidentialPropertiesConstants.Discriminators.Complex - // | t when Type.(=)(t, typeof) -> ResidentialPropertiesConstants.Discriminators.Building - // ) - let private genericWhereMethod = typeof.GetMethods () |> Seq.where (fun m -> m.Name = "Where") @@ -98,6 +139,18 @@ module ObjectListFilterExtensions = let private StringStartsWithMethod = typeof.GetMethod ("StartsWith", [| typeof |]) let private StringEndsWithMethod = typeof.GetMethod ("EndsWith", [| typeof |]) let private StringContainsMethod = typeof.GetMethod ("Contains", [| typeof |]) + let private getEnumerableContainsMethod (memberType : Type) = + match memberType.GetMethods(BindingFlags.Instance &&& BindingFlags.Public).FirstOrDefault(fun m -> m.Name = "Contains" && m.GetParameters().Length = 2) with + | null -> + match typeof.GetMethods(BindingFlags.Static ||| BindingFlags.Public).FirstOrDefault(fun m -> m.Name = "Contains" && m.GetParameters().Length = 2) with + | null -> raise (MissingMemberException "Static 'Contains' method with 2 parameters not found on 'Enumerable' class") + | containsGenericStaticMethod -> + if memberType.IsGenericType && memberType.GenericTypeArguments.Length = 1 then + containsGenericStaticMethod.MakeGenericMethod(memberType.GenericTypeArguments) + else + let ienumerable = memberType.GetType().GetInterfaces().First(fun i -> i.FullName.StartsWith "System.Collections.Generic.IEnumerable`1") + containsGenericStaticMethod.MakeGenericMethod([| ienumerable.GenericTypeArguments[0] |]) + | instanceContainsMethod -> instanceContainsMethod let getField (param : ParameterExpression) fieldName = Expression.PropertyOrField (param, fieldName) @@ -125,7 +178,18 @@ module ObjectListFilterExtensions = | EndsWith f -> Expression.Call (Expression.PropertyOrField (param, f.FieldName), StringEndsWithMethod, Expression.Constant (f.Value)) | Contains f -> - Expression.Call (Expression.PropertyOrField (param, f.FieldName), StringContainsMethod, Expression.Constant (f.Value)) + let ``member`` = Expression.PropertyOrField (param, f.FieldName) + let isEnumerable (memberType: Type) = + not (Type.(=)(memberType, typeof)) + && typeof.IsAssignableFrom(memberType) + && memberType.GetInterfaces().Any(fun i -> i.FullName.StartsWith "System.Collections.Generic.IEnumerable`1") + match ``member``.Member with + | :? PropertyInfo as prop when prop.PropertyType |> isEnumerable -> + Expression.Call (getEnumerableContainsMethod prop.PropertyType, Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) + | :? FieldInfo as field when field.FieldType |> isEnumerable -> + Expression.Call (getEnumerableContainsMethod field.FieldType, Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) + | _ -> + Expression.Call (``member``, StringContainsMethod, Expression.Constant (f.Value)) | OfTypes types -> types |> Seq.map (fun t -> buildTypeDiscriminatorCheck param t) @@ -134,39 +198,7 @@ module ObjectListFilterExtensions = let paramExpr = Expression.PropertyOrField (param, f.FieldName) buildFilterExpr (SourceExpression paramExpr) buildTypeDiscriminatorCheck f.Value - [] - type ObjectListFilterLinqOptions<'T, 'D> ( - [] compareDiscriminator : Expression> | null, - [] getDiscriminatorValue : (Type -> 'D) | null, - [] serializeMemberName : (MemberInfo -> string) | null) = - - member _.CompareDiscriminator = compareDiscriminator |> ValueOption.ofObj - member _.GetDiscriminatorValue = getDiscriminatorValue |> ValueOption.ofObj - //member _.SerializeMemberName = serializeMemberName |> ValueOption.ofObj - - static member None = ObjectListFilterLinqOptions<'T, 'D> (null, null, null) - - static member GetCompareDiscriminator (getDiscriminatorValue : Expression>) = - let tParam = Expression.Parameter (typeof<'T>, "x") - let dParam = Expression.Parameter (typeof<'D>, "d") - let body = Expression.Equal(Expression.Invoke(getDiscriminatorValue, tParam), dParam) - Expression.Lambda> (body, tParam, dParam) - - new (getDiscriminator : Expression>) = ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, null, null) - new (compareDiscriminator : Expression>) = ObjectListFilterLinqOptions<'T, 'D> (compareDiscriminator, null, null) - new (getDiscriminatorValue : Type -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (null, getDiscriminatorValue, null) - //new (serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (null, null, serializeMemberName) - - new (getDiscriminator : Expression>, getDiscriminatorValue : Type -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, getDiscriminatorValue, null) - - //new (getDiscriminator : Expression>, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, null, serializeMemberName) - //new (compareDiscriminator : Expression>, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (compareDiscriminator, null, serializeMemberName) - //new (getDiscriminatorValue : Type -> 'D, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (null, getDiscriminatorValue, serializeMemberName) - -type ObjectListFilter with - - member filter.Apply<'T, 'D> (query : IQueryable<'T>, [] options : ObjectListFilterLinqOptions<'T, 'D>) = - + let apply (options : ObjectListFilterLinqOptions<'T, 'D>) (filter : ObjectListFilter) (query : IQueryable<'T>) = match filter with | NoFilter -> query | _ -> @@ -196,3 +228,18 @@ type ObjectListFilter with whereExpr<'T> query param body // Create and execute the final expression query.Provider.CreateQuery<'T> (queryExpr) + +[] +module ObjectListFilterExtensions = + + open ObjectListFilter + + type ObjectListFilter with + + member inline filter.ApplyTo<'T, 'D> (query : IQueryable<'T>, [] options : ObjectListFilterLinqOptions<'T, 'D>) = + apply options filter query + + type IQueryable<'T> with + + member inline query.Apply (filter : ObjectListFilter, [] options : ObjectListFilterLinqOptions<'T, 'D>) = + apply options filter query From 2dfff1626c79d972de29a510e23cf1b0f7823f3d Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Fri, 14 Mar 2025 18:10:28 +0200 Subject: [PATCH 08/23] Implemented IN case for ObjectListFilter and formatted files --- .../ObjectListFilter.fs | 37 ++++++++----------- .../ObjectListFilterLinqTests.fs | 34 +++++++++++++++++ .../SelectLinqTests.fs | 25 +++++++++---- 3 files changed, 68 insertions(+), 28 deletions(-) diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs index 7f1a6b54a..58c43e273 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs @@ -13,6 +13,7 @@ type ObjectListFilter = | Equals of FieldFilter | GreaterThan of FieldFilter | LessThan of FieldFilter + | In of FieldFilter | StartsWith of FieldFilter | EndsWith of FieldFilter | Contains of FieldFilter @@ -25,10 +26,6 @@ open System.Linq.Expressions open System.Runtime.InteropServices open System.Reflection -/// -/// Represents a value that can be either None or Some. -/// - /// /// Allows to specify discriminator comparison or discriminator getter /// and a function that return discriminator value depending on entity type @@ -62,15 +59,13 @@ open System.Reflection [] type ObjectListFilterLinqOptions<'T, 'D> ( [] compareDiscriminator : Expression> | null, - [] getDiscriminatorValue : (Type -> 'D) | null, - [] serializeMemberName : (MemberInfo -> string) | null + [] getDiscriminatorValue : (Type -> 'D) | null ) = member _.CompareDiscriminator = compareDiscriminator |> ValueOption.ofObj member _.GetDiscriminatorValue = getDiscriminatorValue |> ValueOption.ofObj - //member _.SerializeMemberName = serializeMemberName |> ValueOption.ofObj - static member None = ObjectListFilterLinqOptions<'T, 'D> (null, null, null) + static member None = ObjectListFilterLinqOptions<'T, 'D> (null, null) static member GetCompareDiscriminator (getDiscriminatorValue : Expression>) = let tParam = Expression.Parameter (typeof<'T>, "x") @@ -78,16 +73,11 @@ type ObjectListFilterLinqOptions<'T, 'D> ( let body = Expression.Equal(Expression.Invoke(getDiscriminatorValue, tParam), dParam) Expression.Lambda> (body, tParam, dParam) - new (getDiscriminator : Expression>) = ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, null, null) - new (compareDiscriminator : Expression>) = ObjectListFilterLinqOptions<'T, 'D> (compareDiscriminator, null, null) - new (getDiscriminatorValue : Type -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (null, getDiscriminatorValue, null) - //new (serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (null, null, serializeMemberName) - - new (getDiscriminator : Expression>, getDiscriminatorValue : Type -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, getDiscriminatorValue, null) + new (getDiscriminator : Expression>) = ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, null) + new (compareDiscriminator : Expression>) = ObjectListFilterLinqOptions<'T, 'D> (compareDiscriminator, null) + new (getDiscriminatorValue : Type -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (compareDiscriminator = null , getDiscriminatorValue = getDiscriminatorValue) - //new (getDiscriminator : Expression>, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, null, serializeMemberName) - //new (compareDiscriminator : Expression>, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (compareDiscriminator, null, serializeMemberName) - //new (getDiscriminatorValue : Type -> 'D, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (null, getDiscriminatorValue, serializeMemberName) + new (getDiscriminator : Expression>, getDiscriminatorValue : Type -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, getDiscriminatorValue) /// Contains tooling for working with ObjectListFilter. module ObjectListFilter = @@ -190,6 +180,11 @@ module ObjectListFilter = Expression.Call (getEnumerableContainsMethod field.FieldType, Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) | _ -> Expression.Call (``member``, StringContainsMethod, Expression.Constant (f.Value)) + | In f -> + let ``member`` = Expression.PropertyOrField (param, f.FieldName) + let values = f.Value |> List.map (fun v -> Expression.Equal(``member``, Expression.Constant(v))) + (values |> List.reduce (fun acc expr -> Expression.OrElse(acc, expr))) :> Expression + | OfTypes types -> types |> Seq.map (fun t -> buildTypeDiscriminatorCheck param t) @@ -208,19 +203,19 @@ module ObjectListFilter = | ValueNone, ValueNone -> // use __typename from filter and do type.ToSting() for values let typename = t.FullName - Expression.Equal(Expression.PropertyOrField(param, "__typename"), Expression.Constant(typename)) :> Expression + Expression.Equal (Expression.PropertyOrField (param, "__typename"), Expression.Constant (typename)) :> Expression | ValueSome discExpr, ValueNone -> // use discriminator and do type.ToSting() for values let typename = t.FullName - Expression.Invoke(discExpr, param, Expression.Constant(typename)) :> Expression + Expression.Invoke (discExpr, param, Expression.Constant (typename)) :> Expression | ValueNone, ValueSome discValueFn -> // use __typename from filter and execute discValueFn for values let discriminatorValue = discValueFn t - Expression.Equal(Expression.PropertyOrField(param, "__typename"), Expression.Constant(discriminatorValue)) :> Expression + Expression.Equal (Expression.PropertyOrField (param, "__typename"), Expression.Constant (discriminatorValue)) :> Expression | ValueSome discExpr, ValueSome discValueFn -> // use discriminator and execute discValueFn for values let discriminatorValue = discValueFn t - Expression.Invoke(discExpr, param, Expression.Constant (discriminatorValue)) + Expression.Invoke (discExpr, param, Expression.Constant (discriminatorValue)) let queryExpr = let param = Expression.Parameter (typeof<'T>, "x") diff --git a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs index 06c1f157f..6ad37113c 100644 --- a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs @@ -119,6 +119,40 @@ let ``ObjectListFilter works with OR operator``() = result.Contact |> equals { Email = "b.adams@gmail.com" } result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] +[] +let ``ObjectListFilter works with IN operator for string type field``() = + let filter = + In { FieldName = "firstName"; Value = ["Jeneffer"; "Ben"] } + let queryable = data.AsQueryable() + let filteredData = queryable.Apply(filter) |> Seq.toList + List.length filteredData |> equals 2 + let result = List.head filteredData + result.ID |> equals 4 + result.FirstName |> equals "Ben" + result.LastName |> equals "Adams" + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + let result2 = List.last filteredData + result2.ID |> equals 7 + result2.FirstName |> equals "Jeneffer" + result2.LastName |> equals "Trif" + result2.Contact |> equals { Email = "j.trif@gmail.com" } + result2.Friends |> equals [ { Email = "j.abrams@gmail.com" } ] + +[] +let ``ObjectListFilter works with IN operator for int type field``() = + let filter = + In { FieldName = "id"; Value = [4; 2; 7] } + let queryable = data.AsQueryable() + let filteredData = queryable.Apply(filter) |> Seq.toList + List.length filteredData |> equals 3 + let result = List.head filteredData + result.ID |> equals 4 + result.FirstName |> equals "Ben" + result.LastName |> equals "Adams" + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + [] let ``ObjectListFilter works with FilterField operator``() = let filter = diff --git a/tests/FSharp.Data.GraphQL.Tests/SelectLinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/SelectLinqTests.fs index 4b47580fa..02a8b9ad8 100644 --- a/tests/FSharp.Data.GraphQL.Tests/SelectLinqTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/SelectLinqTests.fs @@ -71,13 +71,24 @@ let linqArgs = Define.Input("after", Nullable StringType) ] let schema = - Schema(Define.Object("RootQuery", - [ Define.Field("people", ListOf Person, "", linqArgs, - fun ctx () -> - let info = ctx.ExecutionInfo - let queryable = data.AsQueryable() - let result = queryable.Apply(info) |> Seq.toList - result) ])) + Schema ( + Define.Object ( + "RootQuery", + [ + Define.Field ( + "people", + ListOf Person, + "", + linqArgs, + fun ctx () -> + let info = ctx.ExecutionInfo + let queryable = data.AsQueryable () + let result = queryable.Apply (info) |> Seq.toList + result + ) + ] + ) + ) let schemaProcessor = Executor(schema) From da759abe635dfe997152f56675d8081db84f5612 Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Fri, 14 Mar 2025 18:16:16 +0200 Subject: [PATCH 09/23] Revert Implemented IN case --- .../ObjectListFilter.fs | 6 ---- .../ObjectListFilterLinqTests.fs | 34 ------------------- 2 files changed, 40 deletions(-) diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs index 58c43e273..f97af6af7 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs @@ -13,7 +13,6 @@ type ObjectListFilter = | Equals of FieldFilter | GreaterThan of FieldFilter | LessThan of FieldFilter - | In of FieldFilter | StartsWith of FieldFilter | EndsWith of FieldFilter | Contains of FieldFilter @@ -180,11 +179,6 @@ module ObjectListFilter = Expression.Call (getEnumerableContainsMethod field.FieldType, Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) | _ -> Expression.Call (``member``, StringContainsMethod, Expression.Constant (f.Value)) - | In f -> - let ``member`` = Expression.PropertyOrField (param, f.FieldName) - let values = f.Value |> List.map (fun v -> Expression.Equal(``member``, Expression.Constant(v))) - (values |> List.reduce (fun acc expr -> Expression.OrElse(acc, expr))) :> Expression - | OfTypes types -> types |> Seq.map (fun t -> buildTypeDiscriminatorCheck param t) diff --git a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs index 6ad37113c..06c1f157f 100644 --- a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs @@ -119,40 +119,6 @@ let ``ObjectListFilter works with OR operator``() = result.Contact |> equals { Email = "b.adams@gmail.com" } result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] -[] -let ``ObjectListFilter works with IN operator for string type field``() = - let filter = - In { FieldName = "firstName"; Value = ["Jeneffer"; "Ben"] } - let queryable = data.AsQueryable() - let filteredData = queryable.Apply(filter) |> Seq.toList - List.length filteredData |> equals 2 - let result = List.head filteredData - result.ID |> equals 4 - result.FirstName |> equals "Ben" - result.LastName |> equals "Adams" - result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] - let result2 = List.last filteredData - result2.ID |> equals 7 - result2.FirstName |> equals "Jeneffer" - result2.LastName |> equals "Trif" - result2.Contact |> equals { Email = "j.trif@gmail.com" } - result2.Friends |> equals [ { Email = "j.abrams@gmail.com" } ] - -[] -let ``ObjectListFilter works with IN operator for int type field``() = - let filter = - In { FieldName = "id"; Value = [4; 2; 7] } - let queryable = data.AsQueryable() - let filteredData = queryable.Apply(filter) |> Seq.toList - List.length filteredData |> equals 3 - let result = List.head filteredData - result.ID |> equals 4 - result.FirstName |> equals "Ben" - result.LastName |> equals "Adams" - result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] - [] let ``ObjectListFilter works with FilterField operator``() = let filter = From 76071c5081fdf335aae5a57f19c0610aaec98d63 Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Fri, 14 Mar 2025 18:45:56 +0200 Subject: [PATCH 10/23] Fixed test ObjectListFilter works with FilterField operator --- .../ObjectListFilterLinqTests.fs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs index 06c1f157f..0391cff34 100644 --- a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs @@ -122,16 +122,16 @@ let ``ObjectListFilter works with OR operator``() = [] let ``ObjectListFilter works with FilterField operator``() = let filter = - FilterField { FieldName = "Friends"; Value = Contains { FieldName = "Email"; Value = "l.trif@gmail.com" } } + FilterField { FieldName = "Contact"; Value = Contains { FieldName = "Email"; Value = "j.trif@gmail.com" } } let queryable = data.AsQueryable() let filteredData = queryable.Apply(filter) |> Seq.toList List.length filteredData |> equals 1 let result = List.head filteredData - result.ID |> equals 4 - result.FirstName |> equals "Ben" - result.LastName |> equals "Adams" - result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + result.ID |> equals 7 + result.FirstName |> equals "Jeneffer" + result.LastName |> equals "Trif" + result.Contact |> equals { Email = "j.trif@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" } ] [] let ``ObjectListFilter works with NOT operator``() = @@ -360,4 +360,3 @@ let ``ObjectListFilter works with Contains operator collection type properties w result1.Name |> equals "Product B" let result2 = List.last filteredData result2.Name |> equals "Product C" - From 1d0f912e7d08355d55cae59aba97abf83403343b Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Fri, 14 Mar 2025 20:22:11 +0200 Subject: [PATCH 11/23] Fixed tests for Contains operator on different collection types --- .../ObjectListFilter.fs | 24 ++++---- .../ObjectListFilterLinqTests.fs | 58 +++++++++++++++---- 2 files changed, 58 insertions(+), 24 deletions(-) diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs index f97af6af7..96a6a256d 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs @@ -129,17 +129,15 @@ module ObjectListFilter = let private StringEndsWithMethod = typeof.GetMethod ("EndsWith", [| typeof |]) let private StringContainsMethod = typeof.GetMethod ("Contains", [| typeof |]) let private getEnumerableContainsMethod (memberType : Type) = - match memberType.GetMethods(BindingFlags.Instance &&& BindingFlags.Public).FirstOrDefault(fun m -> m.Name = "Contains" && m.GetParameters().Length = 2) with - | null -> - match typeof.GetMethods(BindingFlags.Static ||| BindingFlags.Public).FirstOrDefault(fun m -> m.Name = "Contains" && m.GetParameters().Length = 2) with - | null -> raise (MissingMemberException "Static 'Contains' method with 2 parameters not found on 'Enumerable' class") - | containsGenericStaticMethod -> - if memberType.IsGenericType && memberType.GenericTypeArguments.Length = 1 then - containsGenericStaticMethod.MakeGenericMethod(memberType.GenericTypeArguments) - else - let ienumerable = memberType.GetType().GetInterfaces().First(fun i -> i.FullName.StartsWith "System.Collections.Generic.IEnumerable`1") - containsGenericStaticMethod.MakeGenericMethod([| ienumerable.GenericTypeArguments[0] |]) - | instanceContainsMethod -> instanceContainsMethod + match typeof.GetMethods(BindingFlags.Static ||| BindingFlags.Public).FirstOrDefault(fun m -> m.Name = "Contains" && m.GetParameters().Length = 2) with + | null -> raise (MissingMemberException "Static 'Contains' method with 2 parameters not found on 'Enumerable' class") + | containsGenericStaticMethod -> + if memberType.IsGenericType && memberType.GenericTypeArguments.Length = 1 then + containsGenericStaticMethod.MakeGenericMethod(memberType.GenericTypeArguments) + else + let ienumerable = memberType.GetInterfaces().First(fun i -> i.FullName.StartsWith "System.Collections.Generic.IEnumerable`1") + containsGenericStaticMethod.MakeGenericMethod([| ienumerable.GenericTypeArguments[0] |]) + let getField (param : ParameterExpression) fieldName = Expression.PropertyOrField (param, fieldName) @@ -174,7 +172,9 @@ module ObjectListFilter = && memberType.GetInterfaces().Any(fun i -> i.FullName.StartsWith "System.Collections.Generic.IEnumerable`1") match ``member``.Member with | :? PropertyInfo as prop when prop.PropertyType |> isEnumerable -> - Expression.Call (getEnumerableContainsMethod prop.PropertyType, Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) + match prop.PropertyType.GetMethods(BindingFlags.Instance ||| BindingFlags.Public).FirstOrDefault(fun m -> m.Name = "Contains" && m.GetParameters().Length = 1) with + | null -> Expression.Call (getEnumerableContainsMethod prop.PropertyType, Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) + | instanceContainsMethod -> Expression.Call (Expression.PropertyOrField (param, f.FieldName),instanceContainsMethod, Expression.Constant (f.Value)) | :? FieldInfo as field when field.FieldType |> isEnumerable -> Expression.Call (getEnumerableContainsMethod field.FieldType, Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) | _ -> diff --git a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs index 0391cff34..86091b3e1 100644 --- a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs @@ -324,22 +324,20 @@ let ``ObjectListFilter works with getDiscriminatorValue startsWith for Horse and c.ID |> equals 5 c.Name |> equals "Hamster E" -type Product = { +type ListTagsProduct = { Name : string Tags : string list } -let productList = - [ - { Name = "Product A"; Tags = ["Tag1"; "Tag2"] } - { Name = "Product B"; Tags = ["Tag2"; "Tag3"] } - { Name = "Product C"; Tags = ["Tag3"; "Tag4"] } - { Name = "Product D"; Tags = ["Tag4"; "Tag5"] } - ] -let productArray = productList.ToArray() - [] -let ``ObjectListFilter works with Contains operator collection type properties``() = +let ``ObjectListFilter works with Contains operator on list collection properties``() = + let productList = + [ + { Name = "Product A"; Tags = ["Tag1"; "Tag2"] } + { Name = "Product B"; Tags = ["Tag2"; "Tag3"] } + { Name = "Product C"; Tags = ["Tag3"; "Tag4"] } + { Name = "Product D"; Tags = ["Tag4"; "Tag5"] } + ] let queryable = productList.AsQueryable() let filter = Contains { FieldName = "Tags"; Value = "Tag3" } let filteredData = queryable.Apply(filter) |> Seq.toList @@ -349,9 +347,45 @@ let ``ObjectListFilter works with Contains operator collection type properties`` let result2 = List.last filteredData result2.Name |> equals "Product C" +type ArrayTagsProduct = { + Name : string + Tags : string array +} + +[] +let ``ObjectListFilter works with Contains operator on array collection properties``() = + + let productArray = + [ + { Name = "Product A"; Tags = [|"Tag1"; "Tag2"|] } + { Name = "Product B"; Tags = [|"Tag2"; "Tag3"|] } + { Name = "Product C"; Tags = [|"Tag3"; "Tag4"|] } + { Name = "Product D"; Tags = [|"Tag4"; "Tag5"|] } + ] + let queryable = productArray.AsQueryable() + let filter = Contains { FieldName = "Tags"; Value = "Tag3" } + let filteredData = queryable.Apply(filter) |> Seq.toList + List.length filteredData |> equals 2 + let result1 = List.head filteredData + result1.Name |> equals "Product B" + let result2 = List.last filteredData + result2.Name |> equals "Product C" + +type SetTagsProduct = { + Name : string + Tags : string Set +} [] -let ``ObjectListFilter works with Contains operator collection type properties with array``() = +let ``ObjectListFilter works with Contains operator on set collection properties``() = + + let productArray = + [ + { Name = "Product A"; Tags = [|"Tag1"; "Tag2"|] |> Set.ofArray} + { Name = "Product B"; Tags = [|"Tag2"; "Tag3"|] |> Set.ofArray} + { Name = "Product C"; Tags = [|"Tag3"; "Tag4"|] |> Set.ofArray} + { Name = "Product D"; Tags = [|"Tag4"; "Tag5"|] |> Set.ofArray} + ] let queryable = productArray.AsQueryable() let filter = Contains { FieldName = "Tags"; Value = "Tag3" } let filteredData = queryable.Apply(filter) |> Seq.toList From fd37e4a004b188a5bd16c471f9f5f6208670c5c2 Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Mon, 17 Mar 2025 14:36:32 +0200 Subject: [PATCH 12/23] PR fixes --- src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs | 2 -- tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs index 96a6a256d..4ea0f8c45 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs @@ -75,7 +75,6 @@ type ObjectListFilterLinqOptions<'T, 'D> ( new (getDiscriminator : Expression>) = ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, null) new (compareDiscriminator : Expression>) = ObjectListFilterLinqOptions<'T, 'D> (compareDiscriminator, null) new (getDiscriminatorValue : Type -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (compareDiscriminator = null , getDiscriminatorValue = getDiscriminatorValue) - new (getDiscriminator : Expression>, getDiscriminatorValue : Type -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, getDiscriminatorValue) /// Contains tooling for working with ObjectListFilter. @@ -138,7 +137,6 @@ module ObjectListFilter = let ienumerable = memberType.GetInterfaces().First(fun i -> i.FullName.StartsWith "System.Collections.Generic.IEnumerable`1") containsGenericStaticMethod.MakeGenericMethod([| ienumerable.GenericTypeArguments[0] |]) - let getField (param : ParameterExpression) fieldName = Expression.PropertyOrField (param, fieldName) [] diff --git a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs index 86091b3e1..8613dcb52 100644 --- a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs @@ -372,8 +372,8 @@ let ``ObjectListFilter works with Contains operator on array collection properti result2.Name |> equals "Product C" type SetTagsProduct = { - Name : string - Tags : string Set + Name : string + Tags : string Set } [] From e788d7d1ce700b97e97f32b6d1cf7938541d6abf Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Fri, 14 Mar 2025 20:24:20 +0200 Subject: [PATCH 13/23] Revert "Revert Implemented IN case" This reverts commit da759abe635dfe997152f56675d8081db84f5612. --- .../ObjectListFilter.fs | 6 ++++ .../ObjectListFilterLinqTests.fs | 34 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs index 4ea0f8c45..186abcdc1 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs @@ -13,6 +13,7 @@ type ObjectListFilter = | Equals of FieldFilter | GreaterThan of FieldFilter | LessThan of FieldFilter + | In of FieldFilter | StartsWith of FieldFilter | EndsWith of FieldFilter | Contains of FieldFilter @@ -177,6 +178,11 @@ module ObjectListFilter = Expression.Call (getEnumerableContainsMethod field.FieldType, Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) | _ -> Expression.Call (``member``, StringContainsMethod, Expression.Constant (f.Value)) + | In f -> + let ``member`` = Expression.PropertyOrField (param, f.FieldName) + let values = f.Value |> List.map (fun v -> Expression.Equal(``member``, Expression.Constant(v))) + (values |> List.reduce (fun acc expr -> Expression.OrElse(acc, expr))) :> Expression + | OfTypes types -> types |> Seq.map (fun t -> buildTypeDiscriminatorCheck param t) diff --git a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs index 8613dcb52..331576fef 100644 --- a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs @@ -119,6 +119,40 @@ let ``ObjectListFilter works with OR operator``() = result.Contact |> equals { Email = "b.adams@gmail.com" } result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] +[] +let ``ObjectListFilter works with IN operator for string type field``() = + let filter = + In { FieldName = "firstName"; Value = ["Jeneffer"; "Ben"] } + let queryable = data.AsQueryable() + let filteredData = queryable.Apply(filter) |> Seq.toList + List.length filteredData |> equals 2 + let result = List.head filteredData + result.ID |> equals 4 + result.FirstName |> equals "Ben" + result.LastName |> equals "Adams" + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + let result2 = List.last filteredData + result2.ID |> equals 7 + result2.FirstName |> equals "Jeneffer" + result2.LastName |> equals "Trif" + result2.Contact |> equals { Email = "j.trif@gmail.com" } + result2.Friends |> equals [ { Email = "j.abrams@gmail.com" } ] + +[] +let ``ObjectListFilter works with IN operator for int type field``() = + let filter = + In { FieldName = "id"; Value = [4; 2; 7] } + let queryable = data.AsQueryable() + let filteredData = queryable.Apply(filter) |> Seq.toList + List.length filteredData |> equals 3 + let result = List.head filteredData + result.ID |> equals 4 + result.FirstName |> equals "Ben" + result.LastName |> equals "Adams" + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + [] let ``ObjectListFilter works with FilterField operator``() = let filter = From 952757ef980237b35aa01d163a11929b7fd1320a Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Mon, 17 Mar 2025 14:28:06 +0200 Subject: [PATCH 14/23] Implement test `Must parse all filter operations` and join parse field conditions --- .../ObjectListFilter.fs | 5 +- .../SchemaDefinitions.fs | 34 +++-- .../Helpers/ObjAndStructConversions.fs | 7 + .../MiddlewareTests.fs | 141 +++++++++++++++--- .../ObjectListFilterLinqTests.fs | 38 +++++ 5 files changed, 198 insertions(+), 27 deletions(-) diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs index 186abcdc1..2ef661308 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs @@ -12,7 +12,9 @@ type ObjectListFilter = | Not of ObjectListFilter | Equals of FieldFilter | GreaterThan of FieldFilter + | GreaterThanOrEqual of FieldFilter | LessThan of FieldFilter + | LessThanOrEqual of FieldFilter | In of FieldFilter | StartsWith of FieldFilter | EndsWith of FieldFilter @@ -159,6 +161,8 @@ module ObjectListFilter = | Equals f -> Expression.Equal (Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) | GreaterThan f -> Expression.GreaterThan (Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) | LessThan f -> Expression.LessThan (Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) + | GreaterThanOrEqual f -> Expression.GreaterThanOrEqual(Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) + | LessThanOrEqual f -> Expression.LessThanOrEqual (Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) | StartsWith f -> Expression.Call (Expression.PropertyOrField (param, f.FieldName), StringStartsWithMethod, Expression.Constant (f.Value)) | EndsWith f -> @@ -182,7 +186,6 @@ module ObjectListFilter = let ``member`` = Expression.PropertyOrField (param, f.FieldName) let values = f.Value |> List.map (fun v -> Expression.Equal(``member``, Expression.Constant(v))) (values |> List.reduce (fun acc expr -> Expression.OrElse(acc, expr))) :> Expression - | OfTypes types -> types |> Seq.map (fun t -> buildTypeDiscriminatorCheck param t) diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/SchemaDefinitions.fs b/src/FSharp.Data.GraphQL.Server.Middleware/SchemaDefinitions.fs index 449667413..a45ca75a6 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/SchemaDefinitions.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/SchemaDefinitions.fs @@ -12,9 +12,19 @@ open FSharp.Data.GraphQL.Ast let internal removeNoFilter = Seq.where (fun filter -> filter <> NoFilter) +type private ComparisonOperator = +| EndsWith of string +| StartsWith of string +| Contains of string +| Equals of string +| GreaterThan of string +| GreaterThanOrEqual of string +| LessThan of string +| LessThanOrEqual of string + let rec private coerceObjectListFilterInput x : Result = - let (|EndsWith|StartsWith|GreaterThan|LessThan|Contains|Equals|) (s : string) = + let parseFieldCondition (s : string) = let s = s.ToLowerInvariant () let prefix (suffix : string) (s : string) = s.Substring (0, s.Length - suffix.Length) match s with @@ -22,11 +32,15 @@ let rec private coerceObjectListFilterInput x : Result "_ew".Length -> EndsWith (prefix "_ew" s) | s when s.EndsWith ("_starts_with") && s.Length > "_starts_with".Length -> StartsWith (prefix "_starts_with" s) | s when s.EndsWith ("_sw") && s.Length > "_sw".Length -> StartsWith (prefix "_sw" s) + | s when s.EndsWith ("_contains") && s.Length > "_contains".Length -> Contains (prefix "_contains" s) | s when s.EndsWith ("_greater_than") && s.Length > "_greater_than".Length -> GreaterThan (prefix "_greater_than" s) | s when s.EndsWith ("_gt") && s.Length > "_gt".Length -> GreaterThan (prefix "_gt" s) + | s when s.EndsWith ("_greater_than_or_equal") && s.Length > "_greater_than_or_equal".Length -> GreaterThanOrEqual (prefix "_greater_than_or_equal" s) + | s when s.EndsWith ("_gte") && s.Length > "_gte".Length -> GreaterThanOrEqual (prefix "_gte" s) | s when s.EndsWith ("_less_than") && s.Length > "_less_than".Length -> LessThan (prefix "_less_than" s) | s when s.EndsWith ("_lt") && s.Length > "_lt".Length -> LessThan (prefix "_lt" s) - | s when s.EndsWith ("_contains") && s.Length > "_contains".Length -> Contains (prefix "_contains" s) + | s when s.EndsWith ("_less_than_or_equal") && s.Length > "_less_than_or_equal".Length -> LessThanOrEqual (prefix "_less_than_or_equal" s) + | s when s.EndsWith ("_lte") && s.Length > "_lte".Length -> LessThanOrEqual (prefix "_lte" s) | s -> Equals s let (|EquatableValue|Other|) v = @@ -76,7 +90,7 @@ let rec private coerceObjectListFilterInput x : Result Error errs | Ok coerced -> coerced |> removeNoFilter |> Seq.toList |> Ok - match name, value with + match parseFieldCondition name, value with | Equals "and", ListValue fields -> fields |> mapFilters |> Result.map buildAnd | Equals "or", ListValue fields -> fields |> mapFilters |> Result.map buildOr | Equals "not", ObjectValue value -> @@ -84,17 +98,19 @@ let rec private coerceObjectListFilterInput x : Result Error errs | Ok NoFilter -> Ok NoFilter | Ok filter -> Ok (Not filter) - | EndsWith fname, StringValue value -> Ok (EndsWith { FieldName = fname; Value = value }) - | StartsWith fname, StringValue value -> Ok (StartsWith { FieldName = fname; Value = value }) - | Contains fname, StringValue value -> Ok (Contains { FieldName = fname; Value = value }) + | EndsWith fname, StringValue value -> Ok (ObjectListFilter.EndsWith { FieldName = fname; Value = value }) + | StartsWith fname, StringValue value -> Ok (ObjectListFilter.StartsWith { FieldName = fname; Value = value }) + | Contains fname, StringValue value -> Ok (ObjectListFilter.Contains { FieldName = fname; Value = value }) | Equals fname, ObjectValue value -> match mapInput value with | Error errs -> Error errs | Ok NoFilter -> Ok NoFilter | Ok filter -> Ok (FilterField { FieldName = fname; Value = filter }) - | Equals fname, EquatableValue value -> Ok (Equals { FieldName = fname; Value = value }) - | GreaterThan fname, ComparableValue value -> Ok (GreaterThan { FieldName = fname; Value = value }) - | LessThan fname, ComparableValue value -> Ok (LessThan { FieldName = fname; Value = value }) + | Equals fname, EquatableValue value -> Ok (ObjectListFilter.Equals { FieldName = fname; Value = value }) + | GreaterThan fname, ComparableValue value -> Ok (ObjectListFilter.GreaterThan { FieldName = fname; Value = value }) + | GreaterThanOrEqual fname, ComparableValue value -> Ok (ObjectListFilter.GreaterThanOrEqual { FieldName = fname; Value = value }) + | LessThan fname, ComparableValue value -> Ok (ObjectListFilter.LessThan { FieldName = fname; Value = value }) + | LessThanOrEqual fname, ComparableValue value -> Ok (ObjectListFilter.LessThanOrEqual { FieldName = fname; Value = value }) | _ -> Ok NoFilter and mapInput value = diff --git a/src/FSharp.Data.GraphQL.Shared/Helpers/ObjAndStructConversions.fs b/src/FSharp.Data.GraphQL.Shared/Helpers/ObjAndStructConversions.fs index dd78521de..948daf713 100644 --- a/src/FSharp.Data.GraphQL.Shared/Helpers/ObjAndStructConversions.fs +++ b/src/FSharp.Data.GraphQL.Shared/Helpers/ObjAndStructConversions.fs @@ -1,6 +1,7 @@ namespace rec FSharp.Data.GraphQL open System.Linq +open System.Collections.Generic open FsToolkit.ErrorHandling module internal ValueOption = @@ -11,6 +12,12 @@ module internal Option = let mapValueOption mapping voption = voption |> ValueOption.map mapping |> ValueOption.toOption +[] +module KeyValuePair = + + let inline kvp key value = KeyValuePair (key, value) + let inline kvpObj key (value : obj) = KeyValuePair (key, value) + [] module internal ValueTuple = diff --git a/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs b/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs index b936b984b..638780af1 100644 --- a/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs @@ -2,7 +2,10 @@ module FSharp.Data.GraphQL.Tests.MiddlewareTests open System open System.Collections.Generic +open System.Collections.Immutable +open System.Text.Json open Xunit +open FSharp open FSharp.Data.GraphQL open FSharp.Data.GraphQL.Types open FSharp.Data.GraphQL.Server.Middleware @@ -10,8 +13,6 @@ open FSharp.Data.GraphQL.Shared open FSharp.Data.GraphQL.Parser open FSharp.Data.GraphQL.Execution open FSharp.Data.GraphQL.Ast -open System.Collections.Immutable -open System.Text.Json #nowarn "40" @@ -25,7 +26,7 @@ and A = { id : int; value : string; subjects : int list } and B = { id : int; value : string; subjects : int list } -let executor = +let getExecutor (expectedFilter : ObjectListFilter voption) = let a1 : A = { id = 1; value = "A1"; subjects = [ 2; 6 ] } let a2 : A = { id = 2; value = "A2"; subjects = [ 1; 3; 5 ] } let a3 : A = { id = 3; value = "A3"; subjects = [ 1; 2; 4 ] } @@ -70,7 +71,9 @@ let executor = .Field( "subjects", Nullable (ListOf (Nullable SubjectType)), - resolve = fun _ (a : A) -> a.subjects |> List.map getSubject |> List.toSeq |> Some + resolve = fun ctx (a : A) -> + expectedFilter |> ValueOption.iter (fun _ -> equals expectedFilter ctx.Filter) + a.subjects |> List.map getSubject |> List.toSeq |> Some ) .WithQueryWeight (1.0) ] @@ -87,7 +90,9 @@ let executor = .Field( "subjects", Nullable (ListOf (Nullable SubjectType)), - resolve = fun _ (b : B) -> b.subjects |> List.map getSubject |> List.toSeq |> Some + resolve = fun ctx (b : B) -> + expectedFilter |> ValueOption.iter (fun _ -> equals expectedFilter ctx.Filter) + b.subjects |> List.map getSubject |> List.toSeq |> Some ) .WithQueryWeight (1.0) ] @@ -95,7 +100,8 @@ let executor = let Query = Define.Object ( name = "Query", - fields = [ + fields = [ // Перетворити executor на getExecutor(ValueOption filter) *** executor = getExecutor(ValueNone) *** Перевіряти ValueSome на 99 і 100 рядках, дістаючи фільтри з контексту + // Приклад - тести на інпутОбжекти - InputRecordTests: 122 - 150 Define.Field ("A", Nullable AType, "A Field", [ Define.Input ("id", IntType) ], resolve = (fun ctx _ -> getA (ctx.Arg ("id")))) Define.Field ("B", Nullable BType, "B Field", [ Define.Input ("id", IntType) ], resolve = (fun ctx _ -> getB (ctx.Arg ("id")))) ] @@ -108,11 +114,17 @@ let executor = ] Executor (schema, middleware) +let executor = getExecutor(ValueNone) + let execute (query : Document) = executor.AsyncExecute (query) |> sync let executeWithVariables (query : Document, variables : ImmutableDictionary) = executor.AsyncExecute (ast = query, variables = variables) |> sync +let executeWithCustomFilter (query: Document,variables : ImmutableDictionary, customFilter : ObjectListFilter) = + let ex = getExecutor(ValueSome customFilter) + ex.AsyncExecute(ast = query, variables = variables) |> sync + let expectedErrors : GQLProblemDetails list = [ GQLProblemDetails.Create ("Query complexity exceeds maximum threshold. Please reduce query complexity and try again.") ] @@ -495,7 +507,7 @@ let ``Object list filter: must return filter information in Metadata`` () = ] ] let expectedFilter : KeyValuePair = - KeyValuePair ([ "A"; "s" ], And (Equals { FieldName = "id"; Value = 2L }, StartsWith { FieldName = "value"; Value = "A" })) + kvp ([ "A"; "s" ]) (And (Equals { FieldName = "id"; Value = 2L }, StartsWith { FieldName = "value"; Value = "A" })) let result = execute query ensureDirect result <| fun data errors -> empty errors @@ -544,7 +556,7 @@ let ``Object list filter: Must return AND filter information in Metadata`` () = ] ] let expectedFilter : KeyValuePair = - KeyValuePair ([ "A"; "subjects" ], And (StartsWith { FieldName = "value"; Value = "3" }, Equals { FieldName = "id"; Value = 6L })) + kvp ([ "A"; "subjects" ]) (And (StartsWith { FieldName = "value"; Value = "3" }, Equals { FieldName = "id"; Value = 6L })) let result = execute query ensureDirect result <| fun data errors -> empty errors @@ -591,7 +603,7 @@ let ``Object list filter: Must return OR filter information in Metadata`` () = ] ] let expectedFilter : KeyValuePair = - KeyValuePair ([ "A"; "subjects" ], Or (StartsWith { FieldName = "value"; Value = "3" }, Equals { FieldName = "id"; Value = 6L })) + kvp ([ "A"; "subjects" ]) (Or (StartsWith { FieldName = "value"; Value = "3" }, Equals { FieldName = "id"; Value = 6L })) let result = execute query ensureDirect result <| fun data errors -> empty errors @@ -638,7 +650,7 @@ let ``Object list filter: Must return NOT filter information in Metadata`` () = ] ] let expectedFilter : KeyValuePair = - KeyValuePair ([ "A"; "subjects" ], Not (StartsWith { FieldName = "value"; Value = "3" })) + kvp ([ "A"; "subjects" ]) (Not (StartsWith { FieldName = "value"; Value = "3" })) let result = execute query ensureDirect result <| fun data errors -> empty errors @@ -648,11 +660,8 @@ let ``Object list filter: Must return NOT filter information in Metadata`` () = |> seqEquals [ expectedFilter ] [] -let ``Object list filter: Must return filter information in Metadata when supplied as variable`` () = - let jsonString = """{ "not": { "value_starts_with": "3" } }""" - let jsonElement = JsonDocument.Parse(jsonString).RootElement - - let dict = ImmutableDictionary.Empty.Add ("filter", jsonElement) +// Перевірити всі кейси в одному тесті, якщо не вдасться обробити - поміняти Choice на Discriminated Union +let ``Object list filter: Must parse all filter operators`` () = let query = parse """query testQuery($filter: ObjectListFilter!) { @@ -688,12 +697,110 @@ let ``Object list filter: Must return filter information in Metadata when suppli ] ] ] + + let notStartsFilter = """{ "not": { "value_starts_with": "3" } }""" + let notStartsFilterJsonElement = JsonDocument.Parse(notStartsFilter).RootElement + let dict = ImmutableDictionary.Empty.Add ("filter", notStartsFilterJsonElement) let expectedFilter : KeyValuePair = - KeyValuePair ([ "A"; "subjects" ], Not (StartsWith { FieldName = "value"; Value = "3" })) - let result = executeWithVariables (query, dict) + kvp ([ "A"; "subjects" ]) (Not (StartsWith { FieldName = "value"; Value = "3" })) + let result = executeWithCustomFilter (query, dict, Not (StartsWith { FieldName = "value"; Value = "3" })) ensureDirect result <| fun data errors -> empty errors data |> equals (upcast expected) result.Metadata.TryFind ("filters") |> wantValueSome |> seqEquals [ expectedFilter ] + + let notEndsFilter = """{ "not": { "value_ends_with": "2" } }""" + let notEndsFilterJsonElement = JsonDocument.Parse(notEndsFilter).RootElement + let dict1 = ImmutableDictionary.Empty.Add ("filter", notEndsFilterJsonElement) + let expectedFilter1 : KeyValuePair = + kvp ([ "A"; "subjects" ]) (Not (EndsWith { FieldName = "value"; Value = "2" })) + let result1 = executeWithCustomFilter (query, dict1, Not (EndsWith { FieldName = "value"; Value = "2" })) + ensureDirect result1 <| fun data errors1 -> + empty errors1 + data |> equals (upcast expected) + result1.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter1 ] + + let notGreaterThanOrEqualFilter = """{ "not": { "id_gte": 2 } }""" + let notGreaterThanOrEqualFilterJsonElement = JsonDocument.Parse(notGreaterThanOrEqualFilter).RootElement + let dict2 = ImmutableDictionary.Empty.Add ("filter", notGreaterThanOrEqualFilterJsonElement) + let expectedFilter2 : KeyValuePair = + kvp ([ "A"; "subjects" ]) (Not (GreaterThanOrEqual { FieldName = "id"; Value = 2.0 })) + let result2 = executeWithCustomFilter (query, dict2, Not (GreaterThanOrEqual { FieldName = "id"; Value = 2.0 })) + ensureDirect result2 <| fun data errors2 -> + empty errors2 + data |> equals (upcast expected) + result2.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter2 ] + + let notLessThanOrEqualFilter = """{ "not": { "id_lte": 4 } }""" + let notLessThanOrEqualFilterJsonElement = JsonDocument.Parse(notLessThanOrEqualFilter).RootElement + let dict3 = ImmutableDictionary.Empty.Add ("filter", notLessThanOrEqualFilterJsonElement) + let expectedFilter3 : KeyValuePair = + kvp ([ "A"; "subjects" ]) (Not (LessThanOrEqual { FieldName = "id"; Value = 4.0 })) + let result3 = executeWithCustomFilter (query, dict3, Not (LessThanOrEqual { FieldName = "id"; Value = 4.0 })) + ensureDirect result3 <| fun data errors3 -> + empty errors3 + data |> equals (upcast expected) + result3.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter3 ] + + let notGreaterThanFilter = """{ "not": { "id_gt": 2 } }""" + let notGreaterThanFilterJsonElement = JsonDocument.Parse(notGreaterThanFilter).RootElement + let dict4 = ImmutableDictionary.Empty.Add ("filter", notGreaterThanFilterJsonElement) + let expectedFilter4 : KeyValuePair = + kvp ([ "A"; "subjects" ]) (Not (GreaterThan { FieldName = "id"; Value = 2.0 })) + let result4 = executeWithCustomFilter (query, dict4, Not (GreaterThan { FieldName = "id"; Value = 2.0 })) + ensureDirect result4 <| fun data errors2 -> + empty errors2 + data |> equals (upcast expected) + result4.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter4 ] + + let notLessThanFilter = """{ "not": { "id_lt": 4 } }""" + let notLessThanFilterJsonElement = JsonDocument.Parse(notLessThanFilter).RootElement + let dict5 = ImmutableDictionary.Empty.Add ("filter", notLessThanFilterJsonElement) + + let expectedFilter5 : KeyValuePair = + kvp ([ "A"; "subjects" ]) (Not (LessThan { FieldName = "id"; Value = 4.0 })) + let result5 = executeWithCustomFilter (query, dict5, Not (LessThan { FieldName = "id"; Value = 4.0 })) + ensureDirect result5 <| fun data errors5 -> + empty errors5 + data |> equals (upcast expected) + result5.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter5 ] + + let notContainsFilter = """{ "not": { "value_contains": "A" } }""" + let notContainsFilterJsonElement = JsonDocument.Parse(notContainsFilter).RootElement + let dict6 = ImmutableDictionary.Empty.Add ("filter", notContainsFilterJsonElement) + + let expectedFilter6 : KeyValuePair = + kvp ([ "A"; "subjects" ]) (Not (Contains { FieldName = "value"; Value = "A" })) + let result6 = executeWithCustomFilter (query, dict6, Not (Contains { FieldName = "value"; Value = "A" })) + ensureDirect result6 <| fun data errors6 -> + empty errors6 + data |> equals (upcast expected) + result6.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter6 ] + + let notEqualsFilter = """{ "not": { "value": "A2" } }""" + let notEqualsFilterJsonElement = JsonDocument.Parse(notEqualsFilter).RootElement + let dict7 = ImmutableDictionary.Empty.Add ("filter", notEqualsFilterJsonElement) + + let expectedFilter7 : KeyValuePair = + kvp ([ "A"; "subjects" ]) (Not (Equals { FieldName = "value"; Value = "A2" })) + let result7 = executeWithCustomFilter (query, dict7, Not (Equals { FieldName = "value"; Value = "A2" })) + ensureDirect result7 <| fun data errors7 -> + empty errors7 + data |> equals (upcast expected) + result7.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter7 ] diff --git a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs index 331576fef..bb2fca77c 100644 --- a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs @@ -33,6 +33,25 @@ let ``ObjectListFilter works with GreaterThan operator``() = result.Contact |> equals { Email = "j.trif@gmail.com" } result.Friends |> equals [ { Email = "j.abrams@gmail.com" } ] +[] +let ``ObjectListFilter works with GreaterThanOrEqual operator``() = + let filter = GreaterThanOrEqual { FieldName = "id"; Value = 4 } // :> IComparable + let queryable = data.AsQueryable() + let filteredData = queryable.Apply(filter) |> Seq.toList + List.length filteredData |> equals 2 + let result = List.head filteredData + result.ID |> equals 4 + result.FirstName |> equals "Ben" + result.LastName |> equals "Adams" + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + let result1 = List.last filteredData + result1.ID |> equals 7 + result1.FirstName |> equals "Jeneffer" + result1.LastName |> equals "Trif" + result1.Contact |> equals { Email = "j.trif@gmail.com" } + result1.Friends |> equals [ { Email = "j.abrams@gmail.com" } ] + [] let ``ObjectListFilter works with LessThan operator``() = let filter = LessThan { FieldName = "id"; Value = 4 } // :> IComparable @@ -46,6 +65,25 @@ let ``ObjectListFilter works with LessThan operator``() = result.Contact |> equals { Email = "j.abrams@gmail.com" } result.Friends |> equals [] +[] +let ``ObjectListFilter works with LessThanOrEqual operator``() = + let filter = LessThanOrEqual { FieldName = "id"; Value = 4 } // :> IComparable + let queryable = data.AsQueryable() + let filteredData = queryable.Apply(filter) |> Seq.toList + List.length filteredData |> equals 2 + let result1 = List.head filteredData + result1.ID |> equals 4 + result1.FirstName |> equals "Ben" + result1.LastName |> equals "Adams" + result1.Contact |> equals { Email = "b.adams@gmail.com" } + result1.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + let result = List.last filteredData + result.ID |> equals 2 + result.FirstName |> equals "Jonathan" + result.LastName |> equals "Abrams" + result.Contact |> equals { Email = "j.abrams@gmail.com" } + result.Friends |> equals [] + [] let ``ObjectListFilter works with StartsWith operator``() = let filter = StartsWith { FieldName = "firstName"; Value = "J" } From 4b5cc3253b9a709540111f603eabb58ae5e5c43d Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Mon, 17 Mar 2025 16:49:48 +0200 Subject: [PATCH 15/23] Add new operators In, GreaterThanOrEqual, LessThanOrEqual --- .../ObjectListFilter.fs | 108 ++++++++++++------ 1 file changed, 72 insertions(+), 36 deletions(-) diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs index 2ef661308..6329de898 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs @@ -59,10 +59,8 @@ open System.Reflection /// ) /// [] -type ObjectListFilterLinqOptions<'T, 'D> ( - [] compareDiscriminator : Expression> | null, - [] getDiscriminatorValue : (Type -> 'D) | null -) = +type ObjectListFilterLinqOptions<'T, 'D> + ([] compareDiscriminator : Expression> | null, [] getDiscriminatorValue : (Type -> 'D) | null) = member _.CompareDiscriminator = compareDiscriminator |> ValueOption.ofObj member _.GetDiscriminatorValue = getDiscriminatorValue |> ValueOption.ofObj @@ -72,13 +70,16 @@ type ObjectListFilterLinqOptions<'T, 'D> ( static member GetCompareDiscriminator (getDiscriminatorValue : Expression>) = let tParam = Expression.Parameter (typeof<'T>, "x") let dParam = Expression.Parameter (typeof<'D>, "d") - let body = Expression.Equal(Expression.Invoke(getDiscriminatorValue, tParam), dParam) + let body = Expression.Equal (Expression.Invoke (getDiscriminatorValue, tParam), dParam) Expression.Lambda> (body, tParam, dParam) - new (getDiscriminator : Expression>) = ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, null) + new (getDiscriminator : Expression>) = + ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, null) new (compareDiscriminator : Expression>) = ObjectListFilterLinqOptions<'T, 'D> (compareDiscriminator, null) - new (getDiscriminatorValue : Type -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (compareDiscriminator = null , getDiscriminatorValue = getDiscriminatorValue) - new (getDiscriminator : Expression>, getDiscriminatorValue : Type -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, getDiscriminatorValue) + new (getDiscriminatorValue : Type -> 'D) = + ObjectListFilterLinqOptions<'T, 'D> (compareDiscriminator = null, getDiscriminatorValue = getDiscriminatorValue) + new (getDiscriminator : Expression>, getDiscriminatorValue : Type -> 'D) = + ObjectListFilterLinqOptions<'T, 'D> (ObjectListFilterLinqOptions.GetCompareDiscriminator getDiscriminator, getDiscriminatorValue) /// Contains tooling for working with ObjectListFilter. module ObjectListFilter = @@ -94,10 +95,16 @@ module ObjectListFilter = let ( === ) fname value = Equals { FieldName = fname; Value = value } /// Creates a new ObjectListFilter representing a GREATER THAN operation of a comparable value. - let ( ==> ) fname value = GreaterThan { FieldName = fname; Value = value } + let ( >>> ) fname value = GreaterThan { FieldName = fname; Value = value } + + /// Creates a new ObjectListFilter representing a GREATER THAN OR EQUAL operation of a comparable value. + let ( ==> ) fname value = GreaterThanOrEqual { FieldName = fname; Value = value } /// Creates a new ObjectListFilter representing a LESS THAN operation of a comparable value. - let ( <== ) fname value = LessThan { FieldName = fname; Value = value } + let ( <<< ) fname value = LessThan { FieldName = fname; Value = value } + + /// Creates a new ObjectListFilter representing a LESS THAN OR EQUAL operation of a comparable value. + let ( <== ) fname value = LessThanOrEqual { FieldName = fname; Value = value } /// Creates a new ObjectListFilter representing a STARTS WITH operation of a string value. let ( =@@ ) fname value = StartsWith { FieldName = fname; Value = value } @@ -108,6 +115,9 @@ module ObjectListFilter = /// Creates a new ObjectListFilter representing a CONTAINS operation. let ( @=@ ) fname value = Contains { FieldName = fname; Value = value } + /// Creates a new ObjectListFilter representing a IN operation. + let ( =~= ) fname value = In { FieldName = fname; Value = value } + /// Creates a new ObjectListFilter representing a field sub comparison. let ( --> ) fname filter = FilterField { FieldName = fname; Value = filter } @@ -131,15 +141,25 @@ module ObjectListFilter = let private StringEndsWithMethod = typeof.GetMethod ("EndsWith", [| typeof |]) let private StringContainsMethod = typeof.GetMethod ("Contains", [| typeof |]) let private getEnumerableContainsMethod (memberType : Type) = - match typeof.GetMethods(BindingFlags.Static ||| BindingFlags.Public).FirstOrDefault(fun m -> m.Name = "Contains" && m.GetParameters().Length = 2) with + match + typeof + .GetMethods(BindingFlags.Static ||| BindingFlags.Public) + .FirstOrDefault (fun m -> m.Name = "Contains" && m.GetParameters().Length = 2) + with | null -> raise (MissingMemberException "Static 'Contains' method with 2 parameters not found on 'Enumerable' class") | containsGenericStaticMethod -> - if memberType.IsGenericType && memberType.GenericTypeArguments.Length = 1 then - containsGenericStaticMethod.MakeGenericMethod(memberType.GenericTypeArguments) + if + memberType.IsGenericType + && memberType.GenericTypeArguments.Length = 1 + then + containsGenericStaticMethod.MakeGenericMethod (memberType.GenericTypeArguments) else - let ienumerable = memberType.GetInterfaces().First(fun i -> i.FullName.StartsWith "System.Collections.Generic.IEnumerable`1") - containsGenericStaticMethod.MakeGenericMethod([| ienumerable.GenericTypeArguments[0] |]) - + let ienumerable = + memberType + .GetInterfaces() + .First (fun i -> i.FullName.StartsWith "System.Collections.Generic.IEnumerable`1") + containsGenericStaticMethod.MakeGenericMethod ([| ienumerable.GenericTypeArguments[0] |]) + let getField (param : ParameterExpression) fieldName = Expression.PropertyOrField (param, fieldName) [] @@ -161,31 +181,48 @@ module ObjectListFilter = | Equals f -> Expression.Equal (Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) | GreaterThan f -> Expression.GreaterThan (Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) | LessThan f -> Expression.LessThan (Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) - | GreaterThanOrEqual f -> Expression.GreaterThanOrEqual(Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) + | GreaterThanOrEqual f -> Expression.GreaterThanOrEqual (Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) | LessThanOrEqual f -> Expression.LessThanOrEqual (Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) - | StartsWith f -> - Expression.Call (Expression.PropertyOrField (param, f.FieldName), StringStartsWithMethod, Expression.Constant (f.Value)) - | EndsWith f -> - Expression.Call (Expression.PropertyOrField (param, f.FieldName), StringEndsWithMethod, Expression.Constant (f.Value)) + | StartsWith f -> Expression.Call (Expression.PropertyOrField (param, f.FieldName), StringStartsWithMethod, Expression.Constant (f.Value)) + | EndsWith f -> Expression.Call (Expression.PropertyOrField (param, f.FieldName), StringEndsWithMethod, Expression.Constant (f.Value)) | Contains f -> let ``member`` = Expression.PropertyOrField (param, f.FieldName) - let isEnumerable (memberType: Type) = - not (Type.(=)(memberType, typeof)) - && typeof.IsAssignableFrom(memberType) - && memberType.GetInterfaces().Any(fun i -> i.FullName.StartsWith "System.Collections.Generic.IEnumerable`1") - match ``member``.Member with + let isEnumerable (memberType : Type) = + not (Type.(=) (memberType, typeof)) + && typeof.IsAssignableFrom (memberType) + && memberType + .GetInterfaces() + .Any (fun i -> i.FullName.StartsWith "System.Collections.Generic.IEnumerable`1") + match ``member``.Member with | :? PropertyInfo as prop when prop.PropertyType |> isEnumerable -> - match prop.PropertyType.GetMethods(BindingFlags.Instance ||| BindingFlags.Public).FirstOrDefault(fun m -> m.Name = "Contains" && m.GetParameters().Length = 1) with - | null -> Expression.Call (getEnumerableContainsMethod prop.PropertyType, Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) - | instanceContainsMethod -> Expression.Call (Expression.PropertyOrField (param, f.FieldName),instanceContainsMethod, Expression.Constant (f.Value)) + match + prop.PropertyType + .GetMethods(BindingFlags.Instance ||| BindingFlags.Public) + .FirstOrDefault (fun m -> m.Name = "Contains" && m.GetParameters().Length = 1) + with + | null -> + Expression.Call ( + getEnumerableContainsMethod prop.PropertyType, + Expression.PropertyOrField (param, f.FieldName), + Expression.Constant (f.Value) + ) + | instanceContainsMethod -> + Expression.Call (Expression.PropertyOrField (param, f.FieldName), instanceContainsMethod, Expression.Constant (f.Value)) | :? FieldInfo as field when field.FieldType |> isEnumerable -> - Expression.Call (getEnumerableContainsMethod field.FieldType, Expression.PropertyOrField (param, f.FieldName), Expression.Constant (f.Value)) - | _ -> - Expression.Call (``member``, StringContainsMethod, Expression.Constant (f.Value)) + Expression.Call ( + getEnumerableContainsMethod field.FieldType, + Expression.PropertyOrField (param, f.FieldName), + Expression.Constant (f.Value) + ) + | _ -> Expression.Call (``member``, StringContainsMethod, Expression.Constant (f.Value)) | In f -> let ``member`` = Expression.PropertyOrField (param, f.FieldName) - let values = f.Value |> List.map (fun v -> Expression.Equal(``member``, Expression.Constant(v))) - (values |> List.reduce (fun acc expr -> Expression.OrElse(acc, expr))) :> Expression + let values = + f.Value + |> List.map (fun v -> Expression.Equal (``member``, Expression.Constant (v))) + (values + |> List.reduce (fun acc expr -> Expression.OrElse (acc, expr))) + :> Expression | OfTypes types -> types |> Seq.map (fun t -> buildTypeDiscriminatorCheck param t) @@ -237,5 +274,4 @@ module ObjectListFilterExtensions = type IQueryable<'T> with - member inline query.Apply (filter : ObjectListFilter, [] options : ObjectListFilterLinqOptions<'T, 'D>) = - apply options filter query + member inline query.Apply (filter : ObjectListFilter, [] options : ObjectListFilterLinqOptions<'T, 'D>) = apply options filter query From 82d96da4ef42aebc049e9d72d4e33aaa61942a96 Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Mon, 17 Mar 2025 16:25:58 +0200 Subject: [PATCH 16/23] Changed comments in buildTypeDiscriminatorCheck --- .../ObjectListFilter.fs | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs index 6329de898..fbe30c19f 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs @@ -239,22 +239,37 @@ module ObjectListFilter = let buildTypeDiscriminatorCheck (param : SourceExpression) (t : Type) = match options.CompareDiscriminator, options.GetDiscriminatorValue with | ValueNone, ValueNone -> - // use __typename from filter and do type.ToSting() for values - let typename = t.FullName - Expression.Equal (Expression.PropertyOrField (param, "__typename"), Expression.Constant (typename)) :> Expression + Expression.Equal ( + // Default discriminator property + Expression.PropertyOrField (param, "__typename"), + // Default discriminator value + Expression.Constant (t.FullName) + ) :> Expression | ValueSome discExpr, ValueNone -> - // use discriminator and do type.ToSting() for values - let typename = t.FullName - Expression.Invoke (discExpr, param, Expression.Constant (typename)) :> Expression + Expression.Invoke ( + // Provided discriminator comparison + discExpr, + param, + // Default discriminator value gathered from type + Expression.Constant(t.FullName) + ) :> Expression | ValueNone, ValueSome discValueFn -> - // use __typename from filter and execute discValueFn for values let discriminatorValue = discValueFn t - Expression.Equal (Expression.PropertyOrField (param, "__typename"), Expression.Constant (discriminatorValue)) :> Expression + Expression.Equal ( + // Default discriminator property + Expression.PropertyOrField (param, "__typename"), + // Provided discriminator value gathered from type + Expression.Constant (discriminatorValue) + ) :> Expression | ValueSome discExpr, ValueSome discValueFn -> - // use discriminator and execute discValueFn for values let discriminatorValue = discValueFn t - Expression.Invoke (discExpr, param, Expression.Constant (discriminatorValue)) - + Expression.Invoke ( + // Provided discriminator comparison + discExpr, + param, + // Provided discriminator value gathered from type + Expression.Constant (discriminatorValue) + ) let queryExpr = let param = Expression.Parameter (typeof<'T>, "x") let body = buildFilterExpr (SourceExpression param) buildTypeDiscriminatorCheck filter From f4fa72a0c94897584018b89d213397bd437c4a40 Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Mon, 17 Mar 2025 20:25:26 +0200 Subject: [PATCH 17/23] PR fixes --- tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs b/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs index 638780af1..e3d15573a 100644 --- a/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs @@ -755,8 +755,10 @@ let ``Object list filter: Must parse all filter operators`` () = let dict4 = ImmutableDictionary.Empty.Add ("filter", notGreaterThanFilterJsonElement) let expectedFilter4 : KeyValuePair = kvp ([ "A"; "subjects" ]) (Not (GreaterThan { FieldName = "id"; Value = 2.0 })) - let result4 = executeWithCustomFilter (query, dict4, Not (GreaterThan { FieldName = "id"; Value = 2.0 })) - ensureDirect result4 <| fun data errors2 -> + let result4 = + executeWithCustomFilter (query, dict4, Not (GreaterThan { FieldName = "id"; Value = 2.0 })) + ensureDirect result4 + <| fun data errors2 -> empty errors2 data |> equals (upcast expected) result4.Metadata.TryFind ("filters") From 0fd866b2ebe1f1fbec2dac82f68aefd125c4282b Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Mon, 17 Mar 2025 20:25:50 +0200 Subject: [PATCH 18/23] PR fixes --- .../ObjectListFilter.fs | 8 +- .../SchemaDefinitions.fs | 16 +-- .../MiddlewareTests.fs | 136 +++++++++--------- 3 files changed, 75 insertions(+), 85 deletions(-) diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs index fbe30c19f..da0489d8b 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs @@ -217,11 +217,9 @@ module ObjectListFilter = | _ -> Expression.Call (``member``, StringContainsMethod, Expression.Constant (f.Value)) | In f -> let ``member`` = Expression.PropertyOrField (param, f.FieldName) - let values = - f.Value - |> List.map (fun v -> Expression.Equal (``member``, Expression.Constant (v))) - (values - |> List.reduce (fun acc expr -> Expression.OrElse (acc, expr))) + f.Value + |> Seq.map (fun v -> Expression.Equal (``member``, Expression.Constant (v))) + |> Seq.reduce (fun acc expr -> Expression.OrElse (acc, expr)) :> Expression | OfTypes types -> types diff --git a/src/FSharp.Data.GraphQL.Server.Middleware/SchemaDefinitions.fs b/src/FSharp.Data.GraphQL.Server.Middleware/SchemaDefinitions.fs index a45ca75a6..26ad838ee 100644 --- a/src/FSharp.Data.GraphQL.Server.Middleware/SchemaDefinitions.fs +++ b/src/FSharp.Data.GraphQL.Server.Middleware/SchemaDefinitions.fs @@ -13,14 +13,14 @@ open FSharp.Data.GraphQL.Ast let internal removeNoFilter = Seq.where (fun filter -> filter <> NoFilter) type private ComparisonOperator = -| EndsWith of string -| StartsWith of string -| Contains of string -| Equals of string -| GreaterThan of string -| GreaterThanOrEqual of string -| LessThan of string -| LessThanOrEqual of string + | EndsWith of string + | StartsWith of string + | Contains of string + | Equals of string + | GreaterThan of string + | GreaterThanOrEqual of string + | LessThan of string + | LessThanOrEqual of string let rec private coerceObjectListFilterInput x : Result = diff --git a/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs b/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs index e3d15573a..3a309586f 100644 --- a/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs @@ -71,9 +71,11 @@ let getExecutor (expectedFilter : ObjectListFilter voption) = .Field( "subjects", Nullable (ListOf (Nullable SubjectType)), - resolve = fun ctx (a : A) -> - expectedFilter |> ValueOption.iter (fun _ -> equals expectedFilter ctx.Filter) - a.subjects |> List.map getSubject |> List.toSeq |> Some + resolve = + fun ctx (a : A) -> + expectedFilter + |> ValueOption.iter (fun _ -> equals expectedFilter ctx.Filter) + a.subjects |> List.map getSubject |> List.toSeq |> Some ) .WithQueryWeight (1.0) ] @@ -90,9 +92,11 @@ let getExecutor (expectedFilter : ObjectListFilter voption) = .Field( "subjects", Nullable (ListOf (Nullable SubjectType)), - resolve = fun ctx (b : B) -> - expectedFilter |> ValueOption.iter (fun _ -> equals expectedFilter ctx.Filter) - b.subjects |> List.map getSubject |> List.toSeq |> Some + resolve = + fun ctx (b : B) -> + expectedFilter + |> ValueOption.iter (fun _ -> equals expectedFilter ctx.Filter) + b.subjects |> List.map getSubject |> List.toSeq |> Some ) .WithQueryWeight (1.0) ] @@ -100,8 +104,7 @@ let getExecutor (expectedFilter : ObjectListFilter voption) = let Query = Define.Object ( name = "Query", - fields = [ // Перетворити executor на getExecutor(ValueOption filter) *** executor = getExecutor(ValueNone) *** Перевіряти ValueSome на 99 і 100 рядках, дістаючи фільтри з контексту - // Приклад - тести на інпутОбжекти - InputRecordTests: 122 - 150 + fields = [ Define.Field ("A", Nullable AType, "A Field", [ Define.Input ("id", IntType) ], resolve = (fun ctx _ -> getA (ctx.Arg ("id")))) Define.Field ("B", Nullable BType, "B Field", [ Define.Input ("id", IntType) ], resolve = (fun ctx _ -> getB (ctx.Arg ("id")))) ] @@ -114,16 +117,17 @@ let getExecutor (expectedFilter : ObjectListFilter voption) = ] Executor (schema, middleware) -let executor = getExecutor(ValueNone) +let executor = getExecutor (ValueNone) let execute (query : Document) = executor.AsyncExecute (query) |> sync let executeWithVariables (query : Document, variables : ImmutableDictionary) = - executor.AsyncExecute (ast = query, variables = variables) |> sync + executor.AsyncExecute (ast = query, variables = variables) + |> sync -let executeWithCustomFilter (query: Document,variables : ImmutableDictionary, customFilter : ObjectListFilter) = - let ex = getExecutor(ValueSome customFilter) - ex.AsyncExecute(ast = query, variables = variables) |> sync +let executeWithCustomFilter (query : Document, variables : ImmutableDictionary, customFilter : ObjectListFilter) = + let ex = getExecutor (ValueSome customFilter) + ex.AsyncExecute (ast = query, variables = variables) |> sync let expectedErrors : GQLProblemDetails list = [ GQLProblemDetails.Create ("Query complexity exceeds maximum threshold. Please reduce query complexity and try again.") @@ -506,7 +510,7 @@ let ``Object list filter: must return filter information in Metadata`` () = ] ] ] - let expectedFilter : KeyValuePair = + let expectedFilter : KeyValuePair = kvp ([ "A"; "s" ]) (And (Equals { FieldName = "id"; Value = 2L }, StartsWith { FieldName = "value"; Value = "A" })) let result = execute query ensureDirect result <| fun data errors -> @@ -555,7 +559,7 @@ let ``Object list filter: Must return AND filter information in Metadata`` () = ] ] ] - let expectedFilter : KeyValuePair = + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (And (StartsWith { FieldName = "value"; Value = "3" }, Equals { FieldName = "id"; Value = 6L })) let result = execute query ensureDirect result <| fun data errors -> @@ -602,7 +606,7 @@ let ``Object list filter: Must return OR filter information in Metadata`` () = ] ] ] - let expectedFilter : KeyValuePair = + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (Or (StartsWith { FieldName = "value"; Value = "3" }, Equals { FieldName = "id"; Value = 6L })) let result = execute query ensureDirect result <| fun data errors -> @@ -649,7 +653,7 @@ let ``Object list filter: Must return NOT filter information in Metadata`` () = ] ] ] - let expectedFilter : KeyValuePair = + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (Not (StartsWith { FieldName = "value"; Value = "3" })) let result = execute query ensureDirect result <| fun data errors -> @@ -660,7 +664,6 @@ let ``Object list filter: Must return NOT filter information in Metadata`` () = |> seqEquals [ expectedFilter ] [] -// Перевірити всі кейси в одному тесті, якщо не вдасться обробити - поміняти Choice на Discriminated Union let ``Object list filter: Must parse all filter operators`` () = let query = parse @@ -698,12 +701,11 @@ let ``Object list filter: Must parse all filter operators`` () = ] ] - let notStartsFilter = """{ "not": { "value_starts_with": "3" } }""" - let notStartsFilterJsonElement = JsonDocument.Parse(notStartsFilter).RootElement - let dict = ImmutableDictionary.Empty.Add ("filter", notStartsFilterJsonElement) - let expectedFilter : KeyValuePair = - kvp ([ "A"; "subjects" ]) (Not (StartsWith { FieldName = "value"; Value = "3" })) - let result = executeWithCustomFilter (query, dict, Not (StartsWith { FieldName = "value"; Value = "3" })) + let notStartsFilter = """{ "not": { "value_starts_with": "3" } }""" |> JsonDocument.Parse |> _.RootElement + let dict = ImmutableDictionary.Empty.Add ("filter", notStartsFilter) + let filter = Not (StartsWith { FieldName = "value"; Value = "3" }) + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) + let result = executeWithCustomFilter (query, dict, filter) ensureDirect result <| fun data errors -> empty errors data |> equals (upcast expected) @@ -711,12 +713,11 @@ let ``Object list filter: Must parse all filter operators`` () = |> wantValueSome |> seqEquals [ expectedFilter ] - let notEndsFilter = """{ "not": { "value_ends_with": "2" } }""" - let notEndsFilterJsonElement = JsonDocument.Parse(notEndsFilter).RootElement - let dict1 = ImmutableDictionary.Empty.Add ("filter", notEndsFilterJsonElement) - let expectedFilter1 : KeyValuePair = - kvp ([ "A"; "subjects" ]) (Not (EndsWith { FieldName = "value"; Value = "2" })) - let result1 = executeWithCustomFilter (query, dict1, Not (EndsWith { FieldName = "value"; Value = "2" })) + let notEndsFilter = """{ "not": { "value_ends_with": "2" } }""" |> JsonDocument.Parse |> _.RootElement + let dict1 = ImmutableDictionary.Empty.Add ("filter", notEndsFilter) + let filter1 = Not (EndsWith { FieldName = "value"; Value = "2" }) + let expectedFilter1 : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter1) + let result1 = executeWithCustomFilter (query, dict1, filter1) ensureDirect result1 <| fun data errors1 -> empty errors1 data |> equals (upcast expected) @@ -724,12 +725,11 @@ let ``Object list filter: Must parse all filter operators`` () = |> wantValueSome |> seqEquals [ expectedFilter1 ] - let notGreaterThanOrEqualFilter = """{ "not": { "id_gte": 2 } }""" - let notGreaterThanOrEqualFilterJsonElement = JsonDocument.Parse(notGreaterThanOrEqualFilter).RootElement - let dict2 = ImmutableDictionary.Empty.Add ("filter", notGreaterThanOrEqualFilterJsonElement) - let expectedFilter2 : KeyValuePair = - kvp ([ "A"; "subjects" ]) (Not (GreaterThanOrEqual { FieldName = "id"; Value = 2.0 })) - let result2 = executeWithCustomFilter (query, dict2, Not (GreaterThanOrEqual { FieldName = "id"; Value = 2.0 })) + let notGreaterThanOrEqualFilter = """{ "not": { "id_gte": 2 } }""" |> JsonDocument.Parse |> _.RootElement + let dict2 = ImmutableDictionary.Empty.Add ("filter", notGreaterThanOrEqualFilter) + let filter2 = Not (GreaterThanOrEqual { FieldName = "id"; Value = 2.0 }) + let expectedFilter2 : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter2) + let result2 = executeWithCustomFilter (query, dict2,filter2) ensureDirect result2 <| fun data errors2 -> empty errors2 data |> equals (upcast expected) @@ -737,12 +737,11 @@ let ``Object list filter: Must parse all filter operators`` () = |> wantValueSome |> seqEquals [ expectedFilter2 ] - let notLessThanOrEqualFilter = """{ "not": { "id_lte": 4 } }""" - let notLessThanOrEqualFilterJsonElement = JsonDocument.Parse(notLessThanOrEqualFilter).RootElement - let dict3 = ImmutableDictionary.Empty.Add ("filter", notLessThanOrEqualFilterJsonElement) - let expectedFilter3 : KeyValuePair = - kvp ([ "A"; "subjects" ]) (Not (LessThanOrEqual { FieldName = "id"; Value = 4.0 })) - let result3 = executeWithCustomFilter (query, dict3, Not (LessThanOrEqual { FieldName = "id"; Value = 4.0 })) + let notLessThanOrEqualFilter = """{ "not": { "id_lte": 4 } }""" |> JsonDocument.Parse |> _.RootElement + let dict3 = ImmutableDictionary.Empty.Add ("filter", notLessThanOrEqualFilter) + let filter3 = Not (LessThanOrEqual { FieldName = "id"; Value = 4.0 }) + let expectedFilter3 : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter3) + let result3 = executeWithCustomFilter (query, dict3, filter3) ensureDirect result3 <| fun data errors3 -> empty errors3 data |> equals (upcast expected) @@ -750,42 +749,36 @@ let ``Object list filter: Must parse all filter operators`` () = |> wantValueSome |> seqEquals [ expectedFilter3 ] - let notGreaterThanFilter = """{ "not": { "id_gt": 2 } }""" - let notGreaterThanFilterJsonElement = JsonDocument.Parse(notGreaterThanFilter).RootElement - let dict4 = ImmutableDictionary.Empty.Add ("filter", notGreaterThanFilterJsonElement) - let expectedFilter4 : KeyValuePair = - kvp ([ "A"; "subjects" ]) (Not (GreaterThan { FieldName = "id"; Value = 2.0 })) - let result4 = - executeWithCustomFilter (query, dict4, Not (GreaterThan { FieldName = "id"; Value = 2.0 })) - ensureDirect result4 - <| fun data errors2 -> + let notGreaterThanFilter = """{ "not": { "id_gt": 2 } }""" |> JsonDocument.Parse |> _.RootElement + let dict4 = ImmutableDictionary.Empty.Add ("filter", notGreaterThanFilter) + let filter4 = Not (GreaterThan { FieldName = "id"; Value = 2.0 }) + let expectedFilter4 : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter4) + let result4 = executeWithCustomFilter (query, dict4, filter4) + ensureDirect result4 <| fun data errors2 -> empty errors2 data |> equals (upcast expected) result4.Metadata.TryFind ("filters") |> wantValueSome |> seqEquals [ expectedFilter4 ] - let notLessThanFilter = """{ "not": { "id_lt": 4 } }""" - let notLessThanFilterJsonElement = JsonDocument.Parse(notLessThanFilter).RootElement - let dict5 = ImmutableDictionary.Empty.Add ("filter", notLessThanFilterJsonElement) - - let expectedFilter5 : KeyValuePair = - kvp ([ "A"; "subjects" ]) (Not (LessThan { FieldName = "id"; Value = 4.0 })) - let result5 = executeWithCustomFilter (query, dict5, Not (LessThan { FieldName = "id"; Value = 4.0 })) - ensureDirect result5 <| fun data errors5 -> + let notLessThanFilter = """{ "not": { "id_lt": 4 } }""" |> JsonDocument.Parse |> _.RootElement + let dict5 = ImmutableDictionary.Empty.Add ("filter", notLessThanFilter) + let filter5 = Not (LessThan { FieldName = "id"; Value = 4.0 }) + let expectedFilter5 : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter5) + let result5 = executeWithCustomFilter (query, dict5, filter5) + ensureDirect result5 + <| fun data errors5 -> empty errors5 data |> equals (upcast expected) result5.Metadata.TryFind ("filters") |> wantValueSome |> seqEquals [ expectedFilter5 ] - let notContainsFilter = """{ "not": { "value_contains": "A" } }""" - let notContainsFilterJsonElement = JsonDocument.Parse(notContainsFilter).RootElement - let dict6 = ImmutableDictionary.Empty.Add ("filter", notContainsFilterJsonElement) - - let expectedFilter6 : KeyValuePair = - kvp ([ "A"; "subjects" ]) (Not (Contains { FieldName = "value"; Value = "A" })) - let result6 = executeWithCustomFilter (query, dict6, Not (Contains { FieldName = "value"; Value = "A" })) + let notContainsFilter = """{ "not": { "value_contains": "A" } }""" |> JsonDocument.Parse |> _.RootElement + let dict6 = ImmutableDictionary.Empty.Add ("filter", notContainsFilter) + let filter6 = Not (Contains { FieldName = "value"; Value = "A" }) + let expectedFilter6 : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter6) + let result6 = executeWithCustomFilter (query, dict6, filter6) ensureDirect result6 <| fun data errors6 -> empty errors6 data |> equals (upcast expected) @@ -793,13 +786,12 @@ let ``Object list filter: Must parse all filter operators`` () = |> wantValueSome |> seqEquals [ expectedFilter6 ] - let notEqualsFilter = """{ "not": { "value": "A2" } }""" - let notEqualsFilterJsonElement = JsonDocument.Parse(notEqualsFilter).RootElement - let dict7 = ImmutableDictionary.Empty.Add ("filter", notEqualsFilterJsonElement) - + let notEqualsFilter = """{ "not": { "value": "A2" } }""" |> JsonDocument.Parse |> _.RootElement + let dict7 = ImmutableDictionary.Empty.Add ("filter", notEqualsFilter) + let filter7 = Not (Equals { FieldName = "value"; Value = "A2" }) let expectedFilter7 : KeyValuePair = - kvp ([ "A"; "subjects" ]) (Not (Equals { FieldName = "value"; Value = "A2" })) - let result7 = executeWithCustomFilter (query, dict7, Not (Equals { FieldName = "value"; Value = "A2" })) + kvp ([ "A"; "subjects" ]) (filter7) + let result7 = executeWithCustomFilter (query, dict7, filter7) ensureDirect result7 <| fun data errors7 -> empty errors7 data |> equals (upcast expected) From 6c621a1ab445fd5128da22577b0e7200b0a7bcb0 Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Tue, 18 Mar 2025 13:05:16 +0200 Subject: [PATCH 19/23] PR fixes --- .../MiddlewareTests.fs | 203 +++++++++--------- 1 file changed, 105 insertions(+), 98 deletions(-) diff --git a/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs b/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs index 3a309586f..34c069cf8 100644 --- a/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs @@ -700,101 +700,108 @@ let ``Object list filter: Must parse all filter operators`` () = ] ] ] - - let notStartsFilter = """{ "not": { "value_starts_with": "3" } }""" |> JsonDocument.Parse |> _.RootElement - let dict = ImmutableDictionary.Empty.Add ("filter", notStartsFilter) - let filter = Not (StartsWith { FieldName = "value"; Value = "3" }) - let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) - let result = executeWithCustomFilter (query, dict, filter) - ensureDirect result <| fun data errors -> - empty errors - data |> equals (upcast expected) - result.Metadata.TryFind ("filters") - |> wantValueSome - |> seqEquals [ expectedFilter ] - - let notEndsFilter = """{ "not": { "value_ends_with": "2" } }""" |> JsonDocument.Parse |> _.RootElement - let dict1 = ImmutableDictionary.Empty.Add ("filter", notEndsFilter) - let filter1 = Not (EndsWith { FieldName = "value"; Value = "2" }) - let expectedFilter1 : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter1) - let result1 = executeWithCustomFilter (query, dict1, filter1) - ensureDirect result1 <| fun data errors1 -> - empty errors1 - data |> equals (upcast expected) - result1.Metadata.TryFind ("filters") - |> wantValueSome - |> seqEquals [ expectedFilter1 ] - - let notGreaterThanOrEqualFilter = """{ "not": { "id_gte": 2 } }""" |> JsonDocument.Parse |> _.RootElement - let dict2 = ImmutableDictionary.Empty.Add ("filter", notGreaterThanOrEqualFilter) - let filter2 = Not (GreaterThanOrEqual { FieldName = "id"; Value = 2.0 }) - let expectedFilter2 : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter2) - let result2 = executeWithCustomFilter (query, dict2,filter2) - ensureDirect result2 <| fun data errors2 -> - empty errors2 - data |> equals (upcast expected) - result2.Metadata.TryFind ("filters") - |> wantValueSome - |> seqEquals [ expectedFilter2 ] - - let notLessThanOrEqualFilter = """{ "not": { "id_lte": 4 } }""" |> JsonDocument.Parse |> _.RootElement - let dict3 = ImmutableDictionary.Empty.Add ("filter", notLessThanOrEqualFilter) - let filter3 = Not (LessThanOrEqual { FieldName = "id"; Value = 4.0 }) - let expectedFilter3 : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter3) - let result3 = executeWithCustomFilter (query, dict3, filter3) - ensureDirect result3 <| fun data errors3 -> - empty errors3 - data |> equals (upcast expected) - result3.Metadata.TryFind ("filters") - |> wantValueSome - |> seqEquals [ expectedFilter3 ] - - let notGreaterThanFilter = """{ "not": { "id_gt": 2 } }""" |> JsonDocument.Parse |> _.RootElement - let dict4 = ImmutableDictionary.Empty.Add ("filter", notGreaterThanFilter) - let filter4 = Not (GreaterThan { FieldName = "id"; Value = 2.0 }) - let expectedFilter4 : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter4) - let result4 = executeWithCustomFilter (query, dict4, filter4) - ensureDirect result4 <| fun data errors2 -> - empty errors2 - data |> equals (upcast expected) - result4.Metadata.TryFind ("filters") - |> wantValueSome - |> seqEquals [ expectedFilter4 ] - - let notLessThanFilter = """{ "not": { "id_lt": 4 } }""" |> JsonDocument.Parse |> _.RootElement - let dict5 = ImmutableDictionary.Empty.Add ("filter", notLessThanFilter) - let filter5 = Not (LessThan { FieldName = "id"; Value = 4.0 }) - let expectedFilter5 : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter5) - let result5 = executeWithCustomFilter (query, dict5, filter5) - ensureDirect result5 - <| fun data errors5 -> - empty errors5 - data |> equals (upcast expected) - result5.Metadata.TryFind ("filters") - |> wantValueSome - |> seqEquals [ expectedFilter5 ] - - let notContainsFilter = """{ "not": { "value_contains": "A" } }""" |> JsonDocument.Parse |> _.RootElement - let dict6 = ImmutableDictionary.Empty.Add ("filter", notContainsFilter) - let filter6 = Not (Contains { FieldName = "value"; Value = "A" }) - let expectedFilter6 : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter6) - let result6 = executeWithCustomFilter (query, dict6, filter6) - ensureDirect result6 <| fun data errors6 -> - empty errors6 - data |> equals (upcast expected) - result6.Metadata.TryFind ("filters") - |> wantValueSome - |> seqEquals [ expectedFilter6 ] - - let notEqualsFilter = """{ "not": { "value": "A2" } }""" |> JsonDocument.Parse |> _.RootElement - let dict7 = ImmutableDictionary.Empty.Add ("filter", notEqualsFilter) - let filter7 = Not (Equals { FieldName = "value"; Value = "A2" }) - let expectedFilter7 : KeyValuePair = - kvp ([ "A"; "subjects" ]) (filter7) - let result7 = executeWithCustomFilter (query, dict7, filter7) - ensureDirect result7 <| fun data errors7 -> - empty errors7 - data |> equals (upcast expected) - result7.Metadata.TryFind ("filters") - |> wantValueSome - |> seqEquals [ expectedFilter7 ] + do + let notStartsFilter = """{ "not": { "value_starts_with": "3" } }""" |> JsonDocument.Parse |> _.RootElement + let dict = ImmutableDictionary.Empty.Add ("filter", notStartsFilter) + let filter = Not (StartsWith { FieldName = "value"; Value = "3" }) + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) + let result = executeWithCustomFilter (query, dict, filter) + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + result.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter ] + + do + let notEndsFilter = """{ "not": { "value_ends_with": "2" } }""" |> JsonDocument.Parse |> _.RootElement + let dict = ImmutableDictionary.Empty.Add ("filter", notEndsFilter) + let filter = Not (EndsWith { FieldName = "value"; Value = "2" }) + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) + let result = executeWithCustomFilter (query, dict, filter) + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + result.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter ] + + do + let notGreaterThanOrEqualFilter = """{ "not": { "id_gte": 2 } }""" |> JsonDocument.Parse |> _.RootElement + let dict = ImmutableDictionary.Empty.Add ("filter", notGreaterThanOrEqualFilter) + let filter = Not (GreaterThanOrEqual { FieldName = "id"; Value = 2.0 }) + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) + let result = executeWithCustomFilter (query, dict,filter) + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + result.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter ] + + do + let notLessThanOrEqualFilter = """{ "not": { "id_lte": 4 } }""" |> JsonDocument.Parse |> _.RootElement + let dict = ImmutableDictionary.Empty.Add ("filter", notLessThanOrEqualFilter) + let filter = Not (LessThanOrEqual { FieldName = "id"; Value = 4.0 }) + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) + let result = executeWithCustomFilter (query, dict, filter) + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + result.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter ] + + do + let notGreaterThanFilter = """{ "not": { "id_gt": 2 } }""" |> JsonDocument.Parse |> _.RootElement + let dict = ImmutableDictionary.Empty.Add ("filter", notGreaterThanFilter) + let filter = Not (GreaterThan { FieldName = "id"; Value = 2.0 }) + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) + let result = executeWithCustomFilter (query, dict, filter) + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + result.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter ] + + do + let notLessThanFilter = """{ "not": { "id_lt": 4 } }""" |> JsonDocument.Parse |> _.RootElement + let dict = ImmutableDictionary.Empty.Add ("filter", notLessThanFilter) + let filter = Not (LessThan { FieldName = "id"; Value = 4.0 }) + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) + let result = executeWithCustomFilter (query, dict, filter) + ensureDirect result + <| fun data errors -> + empty errors + data |> equals (upcast expected) + result.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter ] + + do + let notContainsFilter = """{ "not": { "value_contains": "A" } }""" |> JsonDocument.Parse |> _.RootElement + let dict = ImmutableDictionary.Empty.Add ("filter", notContainsFilter) + let filter = Not (Contains { FieldName = "value"; Value = "A" }) + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) + let result = executeWithCustomFilter (query, dict, filter) + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + result.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter ] + + do + let notEqualsFilter = """{ "not": { "value": "A2" } }""" |> JsonDocument.Parse |> _.RootElement + let dict = ImmutableDictionary.Empty.Add ("filter", notEqualsFilter) + let filter = Not (Equals { FieldName = "value"; Value = "A2" }) + let expectedFilter : KeyValuePair = + kvp ([ "A"; "subjects" ]) (filter) + let result = executeWithCustomFilter (query, dict, filter) + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + result.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter ] From e11b84756c4659ffd096d550cdf5bb2cff3c79ff Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Tue, 18 Mar 2025 13:17:24 +0200 Subject: [PATCH 20/23] PR fixes --- .../MiddlewareTests.fs | 39 ++-- .../ObjectListFilterLinqTests.fs | 216 ++++++++++-------- 2 files changed, 137 insertions(+), 118 deletions(-) diff --git a/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs b/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs index 34c069cf8..d52e1e7b6 100644 --- a/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs @@ -702,10 +702,10 @@ let ``Object list filter: Must parse all filter operators`` () = ] do let notStartsFilter = """{ "not": { "value_starts_with": "3" } }""" |> JsonDocument.Parse |> _.RootElement - let dict = ImmutableDictionary.Empty.Add ("filter", notStartsFilter) + let variables = ImmutableDictionary.Empty.Add ("filter", notStartsFilter) let filter = Not (StartsWith { FieldName = "value"; Value = "3" }) let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) - let result = executeWithCustomFilter (query, dict, filter) + let result = executeWithCustomFilter (query, variables, filter) ensureDirect result <| fun data errors -> empty errors data |> equals (upcast expected) @@ -715,10 +715,10 @@ let ``Object list filter: Must parse all filter operators`` () = do let notEndsFilter = """{ "not": { "value_ends_with": "2" } }""" |> JsonDocument.Parse |> _.RootElement - let dict = ImmutableDictionary.Empty.Add ("filter", notEndsFilter) + let variables = ImmutableDictionary.Empty.Add ("filter", notEndsFilter) let filter = Not (EndsWith { FieldName = "value"; Value = "2" }) let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) - let result = executeWithCustomFilter (query, dict, filter) + let result = executeWithCustomFilter (query, variables, filter) ensureDirect result <| fun data errors -> empty errors data |> equals (upcast expected) @@ -728,10 +728,10 @@ let ``Object list filter: Must parse all filter operators`` () = do let notGreaterThanOrEqualFilter = """{ "not": { "id_gte": 2 } }""" |> JsonDocument.Parse |> _.RootElement - let dict = ImmutableDictionary.Empty.Add ("filter", notGreaterThanOrEqualFilter) + let variables = ImmutableDictionary.Empty.Add ("filter", notGreaterThanOrEqualFilter) let filter = Not (GreaterThanOrEqual { FieldName = "id"; Value = 2.0 }) let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) - let result = executeWithCustomFilter (query, dict,filter) + let result = executeWithCustomFilter (query, variables,filter) ensureDirect result <| fun data errors -> empty errors data |> equals (upcast expected) @@ -741,10 +741,10 @@ let ``Object list filter: Must parse all filter operators`` () = do let notLessThanOrEqualFilter = """{ "not": { "id_lte": 4 } }""" |> JsonDocument.Parse |> _.RootElement - let dict = ImmutableDictionary.Empty.Add ("filter", notLessThanOrEqualFilter) + let variables = ImmutableDictionary.Empty.Add ("filter", notLessThanOrEqualFilter) let filter = Not (LessThanOrEqual { FieldName = "id"; Value = 4.0 }) let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) - let result = executeWithCustomFilter (query, dict, filter) + let result = executeWithCustomFilter (query, variables, filter) ensureDirect result <| fun data errors -> empty errors data |> equals (upcast expected) @@ -754,10 +754,10 @@ let ``Object list filter: Must parse all filter operators`` () = do let notGreaterThanFilter = """{ "not": { "id_gt": 2 } }""" |> JsonDocument.Parse |> _.RootElement - let dict = ImmutableDictionary.Empty.Add ("filter", notGreaterThanFilter) + let variables = ImmutableDictionary.Empty.Add ("filter", notGreaterThanFilter) let filter = Not (GreaterThan { FieldName = "id"; Value = 2.0 }) let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) - let result = executeWithCustomFilter (query, dict, filter) + let result = executeWithCustomFilter (query, variables, filter) ensureDirect result <| fun data errors -> empty errors data |> equals (upcast expected) @@ -767,10 +767,10 @@ let ``Object list filter: Must parse all filter operators`` () = do let notLessThanFilter = """{ "not": { "id_lt": 4 } }""" |> JsonDocument.Parse |> _.RootElement - let dict = ImmutableDictionary.Empty.Add ("filter", notLessThanFilter) + let variables = ImmutableDictionary.Empty.Add ("filter", notLessThanFilter) let filter = Not (LessThan { FieldName = "id"; Value = 4.0 }) - let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) - let result = executeWithCustomFilter (query, dict, filter) + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) + let result = executeWithCustomFilter (query, variables, filter) ensureDirect result <| fun data errors -> empty errors @@ -781,10 +781,10 @@ let ``Object list filter: Must parse all filter operators`` () = do let notContainsFilter = """{ "not": { "value_contains": "A" } }""" |> JsonDocument.Parse |> _.RootElement - let dict = ImmutableDictionary.Empty.Add ("filter", notContainsFilter) + let variables = ImmutableDictionary.Empty.Add ("filter", notContainsFilter) let filter = Not (Contains { FieldName = "value"; Value = "A" }) - let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) - let result = executeWithCustomFilter (query, dict, filter) + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) + let result = executeWithCustomFilter (query, variables, filter) ensureDirect result <| fun data errors -> empty errors data |> equals (upcast expected) @@ -794,11 +794,10 @@ let ``Object list filter: Must parse all filter operators`` () = do let notEqualsFilter = """{ "not": { "value": "A2" } }""" |> JsonDocument.Parse |> _.RootElement - let dict = ImmutableDictionary.Empty.Add ("filter", notEqualsFilter) + let variables = ImmutableDictionary.Empty.Add ("filter", notEqualsFilter) let filter = Not (Equals { FieldName = "value"; Value = "A2" }) - let expectedFilter : KeyValuePair = - kvp ([ "A"; "subjects" ]) (filter) - let result = executeWithCustomFilter (query, dict, filter) + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) + let result = executeWithCustomFilter (query, variables, filter) ensureDirect result <| fun data errors -> empty errors data |> equals (upcast expected) diff --git a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs index bb2fca77c..85febfd68 100644 --- a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs @@ -39,18 +39,20 @@ let ``ObjectListFilter works with GreaterThanOrEqual operator``() = let queryable = data.AsQueryable() let filteredData = queryable.Apply(filter) |> Seq.toList List.length filteredData |> equals 2 - let result = List.head filteredData - result.ID |> equals 4 - result.FirstName |> equals "Ben" - result.LastName |> equals "Adams" - result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] - let result1 = List.last filteredData - result1.ID |> equals 7 - result1.FirstName |> equals "Jeneffer" - result1.LastName |> equals "Trif" - result1.Contact |> equals { Email = "j.trif@gmail.com" } - result1.Friends |> equals [ { Email = "j.abrams@gmail.com" } ] + do + let result = List.head filteredData + result.ID |> equals 4 + result.FirstName |> equals "Ben" + result.LastName |> equals "Adams" + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + do + let result = List.last filteredData + result.ID |> equals 7 + result.FirstName |> equals "Jeneffer" + result.LastName |> equals "Trif" + result.Contact |> equals { Email = "j.trif@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" } ] [] let ``ObjectListFilter works with LessThan operator``() = @@ -71,18 +73,20 @@ let ``ObjectListFilter works with LessThanOrEqual operator``() = let queryable = data.AsQueryable() let filteredData = queryable.Apply(filter) |> Seq.toList List.length filteredData |> equals 2 - let result1 = List.head filteredData - result1.ID |> equals 4 - result1.FirstName |> equals "Ben" - result1.LastName |> equals "Adams" - result1.Contact |> equals { Email = "b.adams@gmail.com" } - result1.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] - let result = List.last filteredData - result.ID |> equals 2 - result.FirstName |> equals "Jonathan" - result.LastName |> equals "Abrams" - result.Contact |> equals { Email = "j.abrams@gmail.com" } - result.Friends |> equals [] + do + let result = List.head filteredData + result.ID |> equals 4 + result.FirstName |> equals "Ben" + result.LastName |> equals "Adams" + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + do + let result = List.last filteredData + result.ID |> equals 2 + result.FirstName |> equals "Jonathan" + result.LastName |> equals "Abrams" + result.Contact |> equals { Email = "j.abrams@gmail.com" } + result.Friends |> equals [] [] let ``ObjectListFilter works with StartsWith operator``() = @@ -164,18 +168,20 @@ let ``ObjectListFilter works with IN operator for string type field``() = let queryable = data.AsQueryable() let filteredData = queryable.Apply(filter) |> Seq.toList List.length filteredData |> equals 2 - let result = List.head filteredData - result.ID |> equals 4 - result.FirstName |> equals "Ben" - result.LastName |> equals "Adams" - result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] - let result2 = List.last filteredData - result2.ID |> equals 7 - result2.FirstName |> equals "Jeneffer" - result2.LastName |> equals "Trif" - result2.Contact |> equals { Email = "j.trif@gmail.com" } - result2.Friends |> equals [ { Email = "j.abrams@gmail.com" } ] + do + let result = List.head filteredData + result.ID |> equals 4 + result.FirstName |> equals "Ben" + result.LastName |> equals "Adams" + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + do + let result = List.last filteredData + result.ID |> equals 7 + result.FirstName |> equals "Jeneffer" + result.LastName |> equals "Trif" + result.Contact |> equals { Email = "j.trif@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" } ] [] let ``ObjectListFilter works with IN operator for int type field``() = @@ -212,12 +218,12 @@ let ``ObjectListFilter works with NOT operator``() = let queryable = data.AsQueryable() let filteredData = queryable.Apply(filter) |> Seq.toList List.length filteredData |> equals 2 - let result1 = List.head filteredData - result1.ID |> equals 2 - result1.FirstName |> equals "Jonathan" - result1.LastName |> equals "Abrams" - result1.Contact |> equals { Email = "j.abrams@gmail.com" } - result1.Friends |> equals [] + let result = List.head filteredData + result.ID |> equals 2 + result.FirstName |> equals "Jonathan" + result.LastName |> equals "Abrams" + result.Contact |> equals { Email = "j.abrams@gmail.com" } + result.Friends |> equals [] type Complex = { ID : int @@ -263,18 +269,20 @@ let ``ObjectListFilter works with getDiscriminator for Complex``() = | Community c -> c.Discriminator)) let filteredData = queryable.Apply(filter, options) |> Seq.toList List.length filteredData |> equals 2 - let result1 = List.head filteredData - match result1 with - | Complex c -> - c.ID |> equals 1 - c.Name |> equals "Complex A" - | _ -> failwith "Expected Complex" - let result2 = List.last filteredData - match result2 with - | Complex c -> - c.ID |> equals 4 - c.Name |> equals "Complex AA" - | _ -> failwith "Expected Complex" + do + let result = List.head filteredData + match result with + | Complex c -> + c.ID |> equals 1 + c.Name |> equals "Complex A" + | _ -> failwith "Expected Complex" + do + let result = List.last filteredData + match result with + | Complex c -> + c.ID |> equals 4 + c.Name |> equals "Complex AA" + | _ -> failwith "Expected Complex" [] @@ -304,18 +312,20 @@ let ``ObjectListFilter works with getDiscriminator and getDiscriminatorValue for ) let filteredData = queryable.Apply(filter, options) |> Seq.toList List.length filteredData |> equals 2 - let result1 = List.head filteredData - match result1 with - | Complex c -> - c.ID |> equals 1 - c.Name |> equals "Complex A" - | _ -> failwith "Expected Complex" - let result2 = List.last filteredData - match result2 with - | Complex c -> - c.ID |> equals 4 - c.Name |> equals "Complex AA" - | _ -> failwith "Expected Complex" + do + let result = List.head filteredData + match result with + | Complex c -> + c.ID |> equals 1 + c.Name |> equals "Complex A" + | _ -> failwith "Expected Complex" + do + let result = List.last filteredData + match result with + | Complex c -> + c.ID |> equals 4 + c.Name |> equals "Complex AA" + | _ -> failwith "Expected Complex" type Cow = { ID : int @@ -357,16 +367,18 @@ let ``ObjectListFilter works with getDiscriminatorValue for Horse``() = ) let filteredData = queryable.Apply(filter, options) |> Seq.toList List.length filteredData |> equals 2 - let result1 = List.head filteredData - match result1 with - | h -> - h.ID |> equals 2 - h.Name |> equals "Horse B" - let result2 = List.last filteredData - match result2 with - | h -> - h.ID |> equals 4 - h.Name |> equals "Horse D" + do + let result = List.head filteredData + match result with + | h -> + h.ID |> equals 2 + h.Name |> equals "Horse B" + do + let result = List.last filteredData + match result with + | h -> + h.ID |> equals 4 + h.Name |> equals "Horse D" [] let ``ObjectListFilter works with getDiscriminatorValue startsWith for Horse and Hamster``() = @@ -385,16 +397,18 @@ let ``ObjectListFilter works with getDiscriminatorValue startsWith for Horse and ) let filteredData = queryable.Apply(filter, options) |> Seq.toList List.length filteredData |> equals 3 - let result1 = List.head filteredData - match result1 with - | c -> - c.ID |> equals 2 - c.Name |> equals "Horse B" - let result2 = List.last filteredData - match result2 with - | c -> - c.ID |> equals 5 - c.Name |> equals "Hamster E" + do + let result = List.head filteredData + match result with + | c -> + c.ID |> equals 2 + c.Name |> equals "Horse B" + do + let result = List.last filteredData + match result with + | c -> + c.ID |> equals 5 + c.Name |> equals "Hamster E" type ListTagsProduct = { Name : string @@ -414,10 +428,12 @@ let ``ObjectListFilter works with Contains operator on list collection propertie let filter = Contains { FieldName = "Tags"; Value = "Tag3" } let filteredData = queryable.Apply(filter) |> Seq.toList List.length filteredData |> equals 2 - let result1 = List.head filteredData - result1.Name |> equals "Product B" - let result2 = List.last filteredData - result2.Name |> equals "Product C" + do + let result = List.head filteredData + result.Name |> equals "Product B" + do + let result = List.last filteredData + result.Name |> equals "Product C" type ArrayTagsProduct = { Name : string @@ -438,10 +454,12 @@ let ``ObjectListFilter works with Contains operator on array collection properti let filter = Contains { FieldName = "Tags"; Value = "Tag3" } let filteredData = queryable.Apply(filter) |> Seq.toList List.length filteredData |> equals 2 - let result1 = List.head filteredData - result1.Name |> equals "Product B" - let result2 = List.last filteredData - result2.Name |> equals "Product C" + do + let result = List.head filteredData + result.Name |> equals "Product B" + do + let result = List.last filteredData + result.Name |> equals "Product C" type SetTagsProduct = { Name : string @@ -462,7 +480,9 @@ let ``ObjectListFilter works with Contains operator on set collection properties let filter = Contains { FieldName = "Tags"; Value = "Tag3" } let filteredData = queryable.Apply(filter) |> Seq.toList List.length filteredData |> equals 2 - let result1 = List.head filteredData - result1.Name |> equals "Product B" - let result2 = List.last filteredData - result2.Name |> equals "Product C" + do + let result = List.head filteredData + result.Name |> equals "Product B" + do + let result = List.last filteredData + result.Name |> equals "Product C" From c4e7c0ccad6510b04244215765e638df17a64ed1 Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Tue, 18 Mar 2025 13:41:26 +0200 Subject: [PATCH 21/23] PR fixes --- .../ObjectListFilterLinqTests.fs | 434 +++++++++--------- 1 file changed, 218 insertions(+), 216 deletions(-) diff --git a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs index 85febfd68..998aaf932 100644 --- a/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/ObjectListFilterLinqTests.fs @@ -8,10 +8,10 @@ open FSharp.Data.GraphQL.Server.Middleware open FSharp.Data.GraphQL.Tests.LinqTests [] -let ``ObjectListFilter works with Equals operator``() = - let filter = Equals { FieldName = "firstName"; Value = "Jonathan" } // :> IComparable - let queryable = data.AsQueryable() - let filteredData = queryable.Apply(filter) |> Seq.toList +let ``ObjectListFilter works with Equals operator`` () = + let filter = Equals { FieldName = "firstName"; Value = "Jonathan" } // :> IComparable + let queryable = data.AsQueryable () + let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 1 let result = List.head filteredData result.ID |> equals 2 @@ -21,10 +21,10 @@ let ``ObjectListFilter works with Equals operator``() = result.Friends |> equals [] [] -let ``ObjectListFilter works with GreaterThan operator``() = - let filter = GreaterThan { FieldName = "id"; Value = 4 } // :> IComparable - let queryable = data.AsQueryable() - let filteredData = queryable.Apply(filter) |> Seq.toList +let ``ObjectListFilter works with GreaterThan operator`` () = + let filter = GreaterThan { FieldName = "id"; Value = 4 } // :> IComparable + let queryable = data.AsQueryable () + let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 1 let result = List.head filteredData result.ID |> equals 7 @@ -34,18 +34,18 @@ let ``ObjectListFilter works with GreaterThan operator``() = result.Friends |> equals [ { Email = "j.abrams@gmail.com" } ] [] -let ``ObjectListFilter works with GreaterThanOrEqual operator``() = - let filter = GreaterThanOrEqual { FieldName = "id"; Value = 4 } // :> IComparable - let queryable = data.AsQueryable() - let filteredData = queryable.Apply(filter) |> Seq.toList +let ``ObjectListFilter works with GreaterThanOrEqual operator`` () = + let filter = GreaterThanOrEqual { FieldName = "id"; Value = 4 } // :> IComparable + let queryable = data.AsQueryable () + let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 2 - do + do let result = List.head filteredData result.ID |> equals 4 result.FirstName |> equals "Ben" result.LastName |> equals "Adams" - result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] do let result = List.last filteredData result.ID |> equals 7 @@ -55,10 +55,10 @@ let ``ObjectListFilter works with GreaterThanOrEqual operator``() = result.Friends |> equals [ { Email = "j.abrams@gmail.com" } ] [] -let ``ObjectListFilter works with LessThan operator``() = - let filter = LessThan { FieldName = "id"; Value = 4 } // :> IComparable - let queryable = data.AsQueryable() - let filteredData = queryable.Apply(filter) |> Seq.toList +let ``ObjectListFilter works with LessThan operator`` () = + let filter = LessThan { FieldName = "id"; Value = 4 } // :> IComparable + let queryable = data.AsQueryable () + let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 1 let result = List.head filteredData result.ID |> equals 2 @@ -68,18 +68,18 @@ let ``ObjectListFilter works with LessThan operator``() = result.Friends |> equals [] [] -let ``ObjectListFilter works with LessThanOrEqual operator``() = - let filter = LessThanOrEqual { FieldName = "id"; Value = 4 } // :> IComparable - let queryable = data.AsQueryable() - let filteredData = queryable.Apply(filter) |> Seq.toList +let ``ObjectListFilter works with LessThanOrEqual operator`` () = + let filter = LessThanOrEqual { FieldName = "id"; Value = 4 } // :> IComparable + let queryable = data.AsQueryable () + let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 2 do let result = List.head filteredData result.ID |> equals 4 result.FirstName |> equals "Ben" result.LastName |> equals "Adams" - result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] do let result = List.last filteredData result.ID |> equals 2 @@ -89,10 +89,10 @@ let ``ObjectListFilter works with LessThanOrEqual operator``() = result.Friends |> equals [] [] -let ``ObjectListFilter works with StartsWith operator``() = - let filter = StartsWith { FieldName = "firstName"; Value = "J" } - let queryable = data.AsQueryable() - let filteredData = queryable.Apply(filter) |> Seq.toList +let ``ObjectListFilter works with StartsWith operator`` () = + let filter = StartsWith { FieldName = "firstName"; Value = "J" } + let queryable = data.AsQueryable () + let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 2 let result = List.head filteredData result.ID |> equals 2 @@ -102,79 +102,72 @@ let ``ObjectListFilter works with StartsWith operator``() = result.Friends |> equals [] [] -let ``ObjectListFilter works with Contains operator``() = - let filter = Contains { FieldName = "firstName"; Value = "en" } - let queryable = data.AsQueryable() - let filteredData = queryable.Apply(filter) |> Seq.toList +let ``ObjectListFilter works with Contains operator`` () = + let filter = Contains { FieldName = "firstName"; Value = "en" } + let queryable = data.AsQueryable () + let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 2 let result = List.head filteredData result.ID |> equals 4 result.FirstName |> equals "Ben" result.LastName |> equals "Adams" - result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] [] -let ``ObjectListFilter works with EndsWith operator``() = - let filter = EndsWith { FieldName = "lastName"; Value = "ams" } - let queryable = data.AsQueryable() - let filteredData = queryable.Apply(filter) |> Seq.toList +let ``ObjectListFilter works with EndsWith operator`` () = + let filter = EndsWith { FieldName = "lastName"; Value = "ams" } + let queryable = data.AsQueryable () + let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 2 let result = List.head filteredData result.ID |> equals 4 result.FirstName |> equals "Ben" result.LastName |> equals "Adams" - result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] [] -let ``ObjectListFilter works with AND operator``() = +let ``ObjectListFilter works with AND operator`` () = let filter = - And ( - Contains { FieldName = "firstName"; Value = "en" }, - Equals { FieldName = "lastName"; Value = "Adams" } - ) - let queryable = data.AsQueryable() - let filteredData = queryable.Apply(filter) |> Seq.toList + And (Contains { FieldName = "firstName"; Value = "en" }, Equals { FieldName = "lastName"; Value = "Adams" }) + let queryable = data.AsQueryable () + let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 1 let result = List.head filteredData result.ID |> equals 4 result.FirstName |> equals "Ben" result.LastName |> equals "Adams" - result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] [] -let ``ObjectListFilter works with OR operator``() = +let ``ObjectListFilter works with OR operator`` () = let filter = - Or ( - GreaterThan { FieldName = "id"; Value = 4 }, - Equals { FieldName = "lastName"; Value = "Adams" } - ) - let queryable = data.AsQueryable() - let filteredData = queryable.Apply(filter) |> Seq.toList + Or (GreaterThan { FieldName = "id"; Value = 4 }, Equals { FieldName = "lastName"; Value = "Adams" }) + let queryable = data.AsQueryable () + let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 2 let result = List.head filteredData result.ID |> equals 4 result.FirstName |> equals "Ben" result.LastName |> equals "Adams" - result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] [] -let ``ObjectListFilter works with IN operator for string type field``() = - let filter = - In { FieldName = "firstName"; Value = ["Jeneffer"; "Ben"] } - let queryable = data.AsQueryable() - let filteredData = queryable.Apply(filter) |> Seq.toList +let ``ObjectListFilter works with IN operator for string type field`` () = + let filter = In { FieldName = "firstName"; Value = [ "Jeneffer"; "Ben" ] } + let queryable = data.AsQueryable () + let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 2 do let result = List.head filteredData result.ID |> equals 4 result.FirstName |> equals "Ben" result.LastName |> equals "Adams" - result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] do let result = List.last filteredData result.ID |> equals 7 @@ -184,39 +177,40 @@ let ``ObjectListFilter works with IN operator for string type field``() = result.Friends |> equals [ { Email = "j.abrams@gmail.com" } ] [] -let ``ObjectListFilter works with IN operator for int type field``() = - let filter = - In { FieldName = "id"; Value = [4; 2; 7] } - let queryable = data.AsQueryable() - let filteredData = queryable.Apply(filter) |> Seq.toList +let ``ObjectListFilter works with IN operator for int type field`` () = + let filter = In { FieldName = "id"; Value = [ 4; 2; 7 ] } + let queryable = data.AsQueryable () + let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 3 let result = List.head filteredData result.ID |> equals 4 result.FirstName |> equals "Ben" result.LastName |> equals "Adams" - result.Contact |> equals { Email = "b.adams@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] + result.Contact |> equals { Email = "b.adams@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" }; { Email = "l.trif@gmail.com" } ] [] -let ``ObjectListFilter works with FilterField operator``() = +let ``ObjectListFilter works with FilterField operator`` () = let filter = - FilterField { FieldName = "Contact"; Value = Contains { FieldName = "Email"; Value = "j.trif@gmail.com" } } - let queryable = data.AsQueryable() - let filteredData = queryable.Apply(filter) |> Seq.toList + FilterField { + FieldName = "Contact" + Value = Contains { FieldName = "Email"; Value = "j.trif@gmail.com" } + } + let queryable = data.AsQueryable () + let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 1 let result = List.head filteredData result.ID |> equals 7 result.FirstName |> equals "Jeneffer" result.LastName |> equals "Trif" - result.Contact |> equals { Email = "j.trif@gmail.com" } - result.Friends |> equals [ { Email = "j.abrams@gmail.com" } ] + result.Contact |> equals { Email = "j.trif@gmail.com" } + result.Friends |> equals [ { Email = "j.abrams@gmail.com" } ] [] -let ``ObjectListFilter works with NOT operator``() = - let filter = - Not (Equals { FieldName = "lastName"; Value = "Adams" }) - let queryable = data.AsQueryable() - let filteredData = queryable.Apply(filter) |> Seq.toList +let ``ObjectListFilter works with NOT operator`` () = + let filter = Not (Equals { FieldName = "lastName"; Value = "Adams" }) + let queryable = data.AsQueryable () + let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 2 let result = List.head filteredData result.ID |> equals 2 @@ -225,22 +219,17 @@ let ``ObjectListFilter works with NOT operator``() = result.Contact |> equals { Email = "j.abrams@gmail.com" } result.Friends |> equals [] -type Complex = - { ID : int - Name : string - Discriminator : string } +type Complex = { ID : int; Name : string; Discriminator : string } -type Building = - { ID : int - Name : string - Discriminator : string } +type Building = { ID : int; Name : string; Discriminator : string } -type Community = - { ID : int - Name : string - Discriminator : string - Complexes : int list - Buildings : int list } +type Community = { + ID : int + Name : string + Discriminator : string + Complexes : int list + Buildings : int list +} type Property = | Complex of Complex @@ -249,25 +238,37 @@ type Property = [] -let ``ObjectListFilter works with getDiscriminator for Complex``() = - let propertyData: Property list = - [ - Complex { ID = 1; Name = "Complex A"; Discriminator = typeof.FullName } - Building { ID = 2; Name = "Building B"; Discriminator = typeof.FullName } - Community { ID = 3; Name = "Community C"; Discriminator = typeof.FullName; Complexes = [1]; Buildings = [2] } - Complex { ID = 4; Name = "Complex AA"; Discriminator = typeof.FullName } - Building { ID = 5; Name = "Building BB"; Discriminator = typeof.FullName } - Community { ID = 6; Name = "Community CC"; Discriminator = typeof.FullName; Complexes = [4]; Buildings = [5] } - ] - let queryable = propertyData.AsQueryable() - let filter = OfTypes [typeof] +let ``ObjectListFilter works with getDiscriminator for Complex`` () = + let propertyData : Property list = [ + Complex { ID = 1; Name = "Complex A"; Discriminator = typeof.FullName } + Building { ID = 2; Name = "Building B"; Discriminator = typeof.FullName } + Community { + ID = 3 + Name = "Community C" + Discriminator = typeof.FullName + Complexes = [ 1 ] + Buildings = [ 2 ] + } + Complex { ID = 4; Name = "Complex AA"; Discriminator = typeof.FullName } + Building { ID = 5; Name = "Building BB"; Discriminator = typeof.FullName } + Community { + ID = 6 + Name = "Community CC" + Discriminator = typeof.FullName + Complexes = [ 4 ] + Buildings = [ 5 ] + } + ] + let queryable = propertyData.AsQueryable () + let filter = OfTypes [ typeof ] let options = - ObjectListFilterLinqOptions( + ObjectListFilterLinqOptions ( (function | Complex c -> c.Discriminator | Building b -> b.Discriminator - | Community c -> c.Discriminator)) - let filteredData = queryable.Apply(filter, options) |> Seq.toList + | Community c -> c.Discriminator) + ) + let filteredData = queryable.Apply (filter, options) |> Seq.toList List.length filteredData |> equals 2 do let result = List.head filteredData @@ -286,20 +287,31 @@ let ``ObjectListFilter works with getDiscriminator for Complex``() = [] -let ``ObjectListFilter works with getDiscriminator and getDiscriminatorValue for Complex``() = - let propertyData: Property list = - [ - Complex { ID = 1; Name = "Complex A"; Discriminator = typeof.Name} - Building { ID = 2; Name = "Building B"; Discriminator = typeof.Name } - Community { ID = 3; Name = "Community C"; Discriminator = typeof.Name; Complexes = [1]; Buildings = [2] } - Complex { ID = 4; Name = "Complex AA"; Discriminator = typeof.Name } - Building { ID = 5; Name = "Building BB"; Discriminator = typeof.Name } - Community { ID = 6; Name = "Community CC"; Discriminator = typeof.Name; Complexes = [4]; Buildings = [5] } - ] - let queryable = propertyData.AsQueryable() - let filter = OfTypes [typeof] +let ``ObjectListFilter works with getDiscriminator and getDiscriminatorValue for Complex`` () = + let propertyData : Property list = [ + Complex { ID = 1; Name = "Complex A"; Discriminator = typeof.Name } + Building { ID = 2; Name = "Building B"; Discriminator = typeof.Name } + Community { + ID = 3 + Name = "Community C" + Discriminator = typeof.Name + Complexes = [ 1 ] + Buildings = [ 2 ] + } + Complex { ID = 4; Name = "Complex AA"; Discriminator = typeof.Name } + Building { ID = 5; Name = "Building BB"; Discriminator = typeof.Name } + Community { + ID = 6 + Name = "Community CC" + Discriminator = typeof.Name + Complexes = [ 4 ] + Buildings = [ 5 ] + } + ] + let queryable = propertyData.AsQueryable () + let filter = OfTypes [ typeof ] let options = - ObjectListFilterLinqOptions( + ObjectListFilterLinqOptions ( (function | Complex c -> c.Discriminator | Building b -> b.Discriminator @@ -310,7 +322,7 @@ let ``ObjectListFilter works with getDiscriminator and getDiscriminatorValue for | t when t = typeof -> "Community" | _ -> raise (NotSupportedException "Type not supported")) ) - let filteredData = queryable.Apply(filter, options) |> Seq.toList + let filteredData = queryable.Apply (filter, options) |> Seq.toList List.length filteredData |> equals 2 do let result = List.head filteredData @@ -327,45 +339,48 @@ let ``ObjectListFilter works with getDiscriminator and getDiscriminatorValue for c.Name |> equals "Complex AA" | _ -> failwith "Expected Complex" -type Cow = - { ID : int - Name : string - Discriminator : string - __typename : string } - -type Horse = - { ID : int - Name : string - Discriminator : string - __typename : string } - -type Hamster = - { ID : int - Name : string - Discriminator : string - __typename : string } - -let animalData = - [ - { ID = 1; Discriminator="Cow"; Name = "Cow A"; __typename = typeof.Name } - { ID = 2; Discriminator="Horse"; Name = "Horse B"; __typename = typeof.Name } - { ID = 3; Discriminator="Cow"; Name = "Cow C"; __typename = typeof.Name } - { ID = 4; Discriminator="Horse"; Name = "Horse D"; __typename = typeof.Name } - { ID = 5; Discriminator="Hamster"; Name = "Hamster E"; __typename = typeof.Name } - ] +type Cow = { ID : int; Name : string; Discriminator : string; __typename : string } + +type Horse = { ID : int; Name : string; Discriminator : string; __typename : string } + +type Hamster = { ID : int; Name : string; Discriminator : string; __typename : string } + +let animalData = [ + { ID = 1; Discriminator = "Cow"; Name = "Cow A"; __typename = typeof.Name } + { + ID = 2 + Discriminator = "Horse" + Name = "Horse B" + __typename = typeof.Name + } + { ID = 3; Discriminator = "Cow"; Name = "Cow C"; __typename = typeof.Name } + { + ID = 4 + Discriminator = "Horse" + Name = "Horse D" + __typename = typeof.Name + } + { + ID = 5 + Discriminator = "Hamster" + Name = "Hamster E" + __typename = typeof.Name + } +] [] -let ``ObjectListFilter works with getDiscriminatorValue for Horse``() = - let queryable = animalData.AsQueryable() - let filter = OfTypes [typeof] +let ``ObjectListFilter works with getDiscriminatorValue for Horse`` () = + let queryable = animalData.AsQueryable () + let filter = OfTypes [ typeof ] let options = - ObjectListFilterLinqOptions( - getDiscriminatorValue = (function - | t when t = typeof -> t.Name - | t when t = typeof -> t.Name - | _ -> raise (NotSupportedException "Type not supported")) - ) - let filteredData = queryable.Apply(filter, options) |> Seq.toList + ObjectListFilterLinqOptions ( + getDiscriminatorValue = + (function + | t when t = typeof -> t.Name + | t when t = typeof -> t.Name + | _ -> raise (NotSupportedException "Type not supported")) + ) + let filteredData = queryable.Apply (filter, options) |> Seq.toList List.length filteredData |> equals 2 do let result = List.head filteredData @@ -381,21 +396,20 @@ let ``ObjectListFilter works with getDiscriminatorValue for Horse``() = h.Name |> equals "Horse D" [] -let ``ObjectListFilter works with getDiscriminatorValue startsWith for Horse and Hamster``() = - - let queryable = animalData.AsQueryable() +let ``ObjectListFilter works with getDiscriminatorValue startsWith for Horse and Hamster`` () = + let queryable = animalData.AsQueryable () let filter = StartsWith { FieldName = "Discriminator"; Value = "H" } let options = ObjectListFilterLinqOptions ( - (fun entity (discriminator: string) -> - entity.Discriminator.StartsWith discriminator), - getDiscriminatorValue = (function - | t when t = typeof -> t.Name - | t when t = typeof -> t.Name - | t when t = typeof -> t.Name - | _ -> raise (NotSupportedException "Type not supported")) + (fun entity (discriminator : string) -> entity.Discriminator.StartsWith discriminator), + getDiscriminatorValue = + (function + | t when t = typeof -> t.Name + | t when t = typeof -> t.Name + | t when t = typeof -> t.Name + | _ -> raise (NotSupportedException "Type not supported")) ) - let filteredData = queryable.Apply(filter, options) |> Seq.toList + let filteredData = queryable.Apply (filter, options) |> Seq.toList List.length filteredData |> equals 3 do let result = List.head filteredData @@ -410,23 +424,19 @@ let ``ObjectListFilter works with getDiscriminatorValue startsWith for Horse and c.ID |> equals 5 c.Name |> equals "Hamster E" -type ListTagsProduct = { - Name : string - Tags : string list -} +type ListTagsProduct = { Name : string; Tags : string list } [] -let ``ObjectListFilter works with Contains operator on list collection properties``() = - let productList = - [ - { Name = "Product A"; Tags = ["Tag1"; "Tag2"] } - { Name = "Product B"; Tags = ["Tag2"; "Tag3"] } - { Name = "Product C"; Tags = ["Tag3"; "Tag4"] } - { Name = "Product D"; Tags = ["Tag4"; "Tag5"] } - ] - let queryable = productList.AsQueryable() +let ``ObjectListFilter works with Contains operator on list collection properties`` () = + let productList = [ + { Name = "Product A"; Tags = [ "Tag1"; "Tag2" ] } + { Name = "Product B"; Tags = [ "Tag2"; "Tag3" ] } + { Name = "Product C"; Tags = [ "Tag3"; "Tag4" ] } + { Name = "Product D"; Tags = [ "Tag4"; "Tag5" ] } + ] + let queryable = productList.AsQueryable () let filter = Contains { FieldName = "Tags"; Value = "Tag3" } - let filteredData = queryable.Apply(filter) |> Seq.toList + let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 2 do let result = List.head filteredData @@ -435,24 +445,20 @@ let ``ObjectListFilter works with Contains operator on list collection propertie let result = List.last filteredData result.Name |> equals "Product C" -type ArrayTagsProduct = { - Name : string - Tags : string array -} +type ArrayTagsProduct = { Name : string; Tags : string array } [] -let ``ObjectListFilter works with Contains operator on array collection properties``() = - - let productArray = - [ - { Name = "Product A"; Tags = [|"Tag1"; "Tag2"|] } - { Name = "Product B"; Tags = [|"Tag2"; "Tag3"|] } - { Name = "Product C"; Tags = [|"Tag3"; "Tag4"|] } - { Name = "Product D"; Tags = [|"Tag4"; "Tag5"|] } +let ``ObjectListFilter works with Contains operator on array collection properties`` () = + + let productArray = [ + { Name = "Product A"; Tags = [| "Tag1"; "Tag2" |] } + { Name = "Product B"; Tags = [| "Tag2"; "Tag3" |] } + { Name = "Product C"; Tags = [| "Tag3"; "Tag4" |] } + { Name = "Product D"; Tags = [| "Tag4"; "Tag5" |] } ] - let queryable = productArray.AsQueryable() + let queryable = productArray.AsQueryable () let filter = Contains { FieldName = "Tags"; Value = "Tag3" } - let filteredData = queryable.Apply(filter) |> Seq.toList + let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 2 do let result = List.head filteredData @@ -461,24 +467,20 @@ let ``ObjectListFilter works with Contains operator on array collection properti let result = List.last filteredData result.Name |> equals "Product C" -type SetTagsProduct = { - Name : string - Tags : string Set -} +type SetTagsProduct = { Name : string; Tags : string Set } [] -let ``ObjectListFilter works with Contains operator on set collection properties``() = - - let productArray = - [ - { Name = "Product A"; Tags = [|"Tag1"; "Tag2"|] |> Set.ofArray} - { Name = "Product B"; Tags = [|"Tag2"; "Tag3"|] |> Set.ofArray} - { Name = "Product C"; Tags = [|"Tag3"; "Tag4"|] |> Set.ofArray} - { Name = "Product D"; Tags = [|"Tag4"; "Tag5"|] |> Set.ofArray} +let ``ObjectListFilter works with Contains operator on set collection properties`` () = + + let productArray = [ + { Name = "Product A"; Tags = [| "Tag1"; "Tag2" |] |> Set.ofArray } + { Name = "Product B"; Tags = [| "Tag2"; "Tag3" |] |> Set.ofArray } + { Name = "Product C"; Tags = [| "Tag3"; "Tag4" |] |> Set.ofArray } + { Name = "Product D"; Tags = [| "Tag4"; "Tag5" |] |> Set.ofArray } ] - let queryable = productArray.AsQueryable() + let queryable = productArray.AsQueryable () let filter = Contains { FieldName = "Tags"; Value = "Tag3" } - let filteredData = queryable.Apply(filter) |> Seq.toList + let filteredData = queryable.Apply (filter) |> Seq.toList List.length filteredData |> equals 2 do let result = List.head filteredData From a5f57633510df354762a91fbff64924d06e436e2 Mon Sep 17 00:00:00 2001 From: Viktor Tochonov Date: Tue, 18 Mar 2025 14:19:40 +0200 Subject: [PATCH 22/23] PR fixes --- .../MiddlewareTests.fs | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs b/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs index d52e1e7b6..d4fcbae9d 100644 --- a/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs @@ -726,6 +726,85 @@ let ``Object list filter: Must parse all filter operators`` () = |> wantValueSome |> seqEquals [ expectedFilter ] + do + let notStartsFilter = """{ "not": { "value_sw": "3" } }""" |> JsonDocument.Parse |> _.RootElement + let variables = ImmutableDictionary.Empty.Add ("filter", notStartsFilter) + let filter = Not (StartsWith { FieldName = "value"; Value = "3" }) + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) + let result = executeWithCustomFilter (query, variables, filter) + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + result.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter ] + + do + let notEndsFilter = """{ "not": { "value_ew": "2" } }""" |> JsonDocument.Parse |> _.RootElement + let variables = ImmutableDictionary.Empty.Add ("filter", notEndsFilter) + let filter = Not (EndsWith { FieldName = "value"; Value = "2" }) + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) + let result = executeWithCustomFilter (query, variables, filter) + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + result.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter ] + + do + let notGreaterThanOrEqualFilter = """{ "not": { "id_greater_than_or_equal": 2 } }""" |> JsonDocument.Parse |> _.RootElement + let variables = ImmutableDictionary.Empty.Add ("filter", notGreaterThanOrEqualFilter) + let filter = Not (GreaterThanOrEqual { FieldName = "id"; Value = 2.0 }) + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) + let result = executeWithCustomFilter (query, variables,filter) + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + result.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter ] + + do + let notLessThanOrEqualFilter = """{ "not": { "id_less_than_or_equal": 4 } }""" |> JsonDocument.Parse |> _.RootElement + let variables = ImmutableDictionary.Empty.Add ("filter", notLessThanOrEqualFilter) + let filter = Not (LessThanOrEqual { FieldName = "id"; Value = 4.0 }) + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) + let result = executeWithCustomFilter (query, variables, filter) + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + result.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter ] + + do + let notGreaterThanFilter = """{ "not": { "id_greater_than": 2 } }""" |> JsonDocument.Parse |> _.RootElement + let variables = ImmutableDictionary.Empty.Add ("filter", notGreaterThanFilter) + let filter = Not (GreaterThan { FieldName = "id"; Value = 2.0 }) + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) + let result = executeWithCustomFilter (query, variables, filter) + ensureDirect result <| fun data errors -> + empty errors + data |> equals (upcast expected) + result.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter ] + + do + let notLessThanFilter = """{ "not": { "id_less_than": 4 } }""" |> JsonDocument.Parse |> _.RootElement + let variables = ImmutableDictionary.Empty.Add ("filter", notLessThanFilter) + let filter = Not (LessThan { FieldName = "id"; Value = 4.0 }) + let expectedFilter : KeyValuePair = kvp ([ "A"; "subjects" ]) (filter) + let result = executeWithCustomFilter (query, variables, filter) + ensureDirect result + <| fun data errors -> + empty errors + data |> equals (upcast expected) + result.Metadata.TryFind ("filters") + |> wantValueSome + |> seqEquals [ expectedFilter ] + do let notGreaterThanOrEqualFilter = """{ "not": { "id_gte": 2 } }""" |> JsonDocument.Parse |> _.RootElement let variables = ImmutableDictionary.Empty.Add ("filter", notGreaterThanOrEqualFilter) From eda5c89f4e6533963fd11dc3e1deb374fb805b20 Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Tue, 18 Mar 2025 16:57:34 +0200 Subject: [PATCH 23/23] Fixed test name --- tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs b/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs index d4fcbae9d..7d6f68abf 100644 --- a/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs +++ b/tests/FSharp.Data.GraphQL.Tests/MiddlewareTests.fs @@ -664,7 +664,7 @@ let ``Object list filter: Must return NOT filter information in Metadata`` () = |> seqEquals [ expectedFilter ] [] -let ``Object list filter: Must parse all filter operators`` () = +let ``Object list filter: Must return filter information in Metadata when supplied as variable and parse all filter operators`` () = let query = parse """query testQuery($filter: ObjectListFilter!) {