Skip to content

Commit 0fa9d2a

Browse files
author
Viktor Tochonov
committed
Implemented new tests for different cases of buildTypeDiscriminatorCheck
1 parent 51ef7f8 commit 0fa9d2a

File tree

3 files changed

+188
-18
lines changed

3 files changed

+188
-18
lines changed

src/FSharp.Data.GraphQL.Server.Middleware/ObjectListFilter.fs

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
namespace FSharp.Data.GraphQL.Server.Middleware
22

33
open System
4-
open System.Linq
5-
open System.Linq.Expressions
6-
open System.Runtime.InteropServices
74

85
/// A filter definition for a field value.
96
type FieldFilter<'Val> = { FieldName : string; Value : 'Val }
@@ -19,7 +16,7 @@ type ObjectListFilter =
1916
| StartsWith of FieldFilter<string>
2017
| EndsWith of FieldFilter<string>
2118
| Contains of FieldFilter<string>
22-
| OfTypes of FieldFilter<Type list>
19+
| OfTypes of Type list
2320
| FilterField of FieldFilter<ObjectListFilter>
2421
| NoFilter
2522

@@ -57,9 +54,15 @@ module ObjectListFilter =
5754
/// Creates a new ObjectListFilter representing a NOT opreation for the existing one.
5855
let ( !!! ) filter = Not filter
5956

57+
open System.Linq
58+
open System.Linq.Expressions
59+
open System.Runtime.InteropServices
60+
open System.Reflection
61+
6062
[<AutoOpen>]
6163
module ObjectListFilterExtensions =
6264

65+
6366
//// discriminator custom condition
6467
//let a () =
6568
// filter.Apply(
@@ -79,6 +82,31 @@ module ObjectListFilterExtensions =
7982
// | t when Type.(=)(t, typeof<Building>) -> ResidentialPropertiesConstants.Discriminators.Building
8083
// )
8184

85+
type DiscriminatorExpression<'T, 'D> =
86+
| GetDiscriminatorValue of ('T -> 'D)
87+
| CompareDiscriminator of Expression<Func<'T, 'D, bool>>
88+
89+
[<Struct>]
90+
type ObjectListFilterLinqOptions<'T, 'D> (
91+
discriminatorExpression : DiscriminatorExpression<'T, 'D> | null,
92+
[<Optional>] getDiscriminatorValue: (Type -> 'D) | null,
93+
[<Optional>] serializeMemberName: (MemberInfo -> string) | null) =
94+
95+
member _.DiscriminatorExpression = discriminatorExpression |> ValueOption.ofObj
96+
member _.GetDiscriminatorValue = getDiscriminatorValue |> ValueOption.ofObj
97+
member _.SerializeMemberName = serializeMemberName |> ValueOption.ofObj
98+
99+
static member None = ObjectListFilterLinqOptions<'T, 'D> (null, null, null)
100+
101+
new (getDiscriminatorValue : 'T -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (GetDiscriminatorValue getDiscriminatorValue, null, null)
102+
new (compareDiscriminator : Expression<Func<'T, 'D, bool>>) = ObjectListFilterLinqOptions<'T, 'D> (CompareDiscriminator compareDiscriminator, null, null)
103+
new (getDiscriminatorValue : Type -> 'D) = ObjectListFilterLinqOptions<'T, 'D> (null, getDiscriminatorValue, null)
104+
new (serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (null, null, serializeMemberName)
105+
106+
new (getDiscriminatorValue : 'T -> 'D, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (GetDiscriminatorValue getDiscriminatorValue, null, serializeMemberName)
107+
new (compareDiscriminator : Expression<Func<'T, 'D, bool>>, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (CompareDiscriminator compareDiscriminator, null, serializeMemberName)
108+
new (getDiscriminatorValue : Type -> 'D, serializeMemberName : MemberInfo -> string) = ObjectListFilterLinqOptions<'T, 'D> (null, getDiscriminatorValue, serializeMemberName)
109+
82110
// Helper to create lambda from body expression
83111
let makeLambda<'T> (param : ParameterExpression) (body : Expression) =
84112
let delegateType = typedefof<Func<_, _>>.MakeGenericType ([| typeof<'T>; body.Type |])
@@ -129,7 +157,7 @@ module ObjectListFilterExtensions =
129157
| Contains f ->
130158
Expression.Call (Expression.PropertyOrField (param, f.FieldName), StringContainsMethod, Expression.Constant (f.Value))
131159
| OfTypes types ->
132-
types.Value
160+
types
133161
|> Seq.map (fun t -> buildTypeDiscriminatorCheck param t)
134162
|> Seq.reduce (fun acc expr -> Expression.Or (acc, expr))
135163
| FilterField f ->
@@ -181,19 +209,22 @@ type ObjectListFilter with
181209
// Helper for discriminator comparison
182210
let buildTypeDiscriminatorCheck (param : SourceExpression) (t : Type) =
183211
match getDiscriminator, getDiscriminatorValue with
184-
| null, discValueFn when obj.Equals (discValueFn, null) ->
212+
| null, discValueFn when obj.Equals(discValueFn, null) ->
185213
// use __typename from filter and do type.ToSting() for values
186-
Unchecked.defaultof<Expression>
187-
| discExpr, discValueFn when obj.Equals (discValueFn, null) ->
214+
let typename = t.FullName
215+
Expression.Equal(Expression.PropertyOrField(param, "__typename"), Expression.Constant(typename)) :> Expression
216+
| discExpr, discValueFn when obj.Equals(discValueFn, null) ->
188217
// use discriminator and do type.ToSting() for values
189-
Unchecked.defaultof<Expression>
218+
let typename = t.FullName
219+
Expression.Equal(Expression.Invoke(discExpr, param), Expression.Constant(typename)) :> Expression
190220
| null, discValueFn ->
191221
// use __typename from filter and execute discValueFn for values
192-
Unchecked.defaultof<Expression>
222+
let discriminatorValue = discValueFn t
223+
Expression.Equal(Expression.PropertyOrField(param, "__typename"), Expression.Constant(discriminatorValue)) :> Expression
193224
| discExpr, discValueFn ->
194225
// use discriminator and execute discValueFn for values
195226
let discriminatorValue = discValueFn t
196-
Expression.Equal (Expression.PropertyOrField (param, "__discriminator"), Expression.Constant (discriminatorValue))
227+
Expression.Equal (Expression.Invoke(discExpr, param), Expression.Constant (discriminatorValue))
197228

198229
let queryExpr =
199230
let param = Expression.Parameter (typeof<'T>, "x")

src/FSharp.Data.GraphQL.Server.Middleware/TypeSystemExtensions.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ module TypeSystemExtensions =
4040
| ValueNone -> raise (MalformedGQLQueryException ($"Type '{name}' not found in schema."))
4141
match typeFields.Keys |> Seq.map getType |> Seq.toList with
4242
| [] -> ValueNone
43-
| filters -> ValueSome (f &&& (OfTypes { FieldName = "__typename"; Value = filters }))
43+
| filters -> ValueSome (f &&& (OfTypes filters))
4444
| _ -> ValueSome f
4545
| false, _ -> ValueNone
4646
| true, _ -> raise (InvalidOperationException "Invalid filter argument type.")

tests/FSharp.Data.GraphQL.Tests/LinqTests.fs

Lines changed: 145 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ open FSharp.Data.GraphQL.Execution
1212
open FSharp.Data.GraphQL.Server.Middleware
1313
open FSharp.Data.GraphQL.Server.Middleware.ObjectListFilter
1414
open FSharp.Data.GraphQL.Server.Middleware.ObjectListFilterExtensions
15+
open System.Linq.Expressions
1516

1617
type Contact =
1718
{ Email : string }
@@ -54,6 +55,7 @@ let data =
5455
Contact = { Email = "[email protected]" }
5556
Friends = [ { Email = "[email protected]" } ] } ]
5657

58+
5759
let internal undefined<'t> = Unchecked.defaultof<'t>
5860

5961
let resolveRoot ctx () =
@@ -352,7 +354,7 @@ let ``ObjectListFilter works with Contains operator``() =
352354
result.FirstName |> equals "Ben"
353355
result.LastName |> equals "Adams"
354356
result.Contact |> equals { Email = "[email protected]" }
355-
result.Friends |> equals [ { Email = "[email protected]" }; { Email = "[email protected]" } ]
357+
result.Friends |> equals [ { Email = "[email protected]" }; { Email = "[email protected]" } ]
356358

357359
[<Fact>]
358360
let ``ObjectListFilter works with EndsWith operator``() =
@@ -365,11 +367,11 @@ let ``ObjectListFilter works with EndsWith operator``() =
365367
result.FirstName |> equals "Ben"
366368
result.LastName |> equals "Adams"
367369
result.Contact |> equals { Email = "[email protected]" }
368-
result.Friends |> equals [ { Email = "[email protected]" }; { Email = "[email protected]" } ]
370+
result.Friends |> equals [ { Email = "[email protected]" }; { Email = "[email protected]" } ]
369371

370372
[<Fact>]
371373
let ``ObjectListFilter works with AND operator``() =
372-
let filter =
374+
let filter =
373375
And (
374376
Contains { FieldName = "firstName"; Value = "en" },
375377
Equals { FieldName = "lastName"; Value = "Adams" }
@@ -386,7 +388,7 @@ let ``ObjectListFilter works with AND operator``() =
386388

387389
[<Fact>]
388390
let ``ObjectListFilter works with OR operator``() =
389-
let filter =
391+
let filter =
390392
Or (
391393
GreaterThan { FieldName = "id"; Value = 4 },
392394
Equals { FieldName = "lastName"; Value = "Adams" }
@@ -403,7 +405,7 @@ let ``ObjectListFilter works with OR operator``() =
403405

404406
//[<Fact>]
405407
//let ``ObjectListFilter works with FilterField operator``() =
406-
// let filter =
408+
// let filter =
407409
// FilterField { FieldName = "Friends"; Value = Contains { FieldName = "Email"; Value = "[email protected]" } }
408410
// let queryable = data.AsQueryable()
409411
// let filteredData = filter.Apply(queryable) |> Seq.toList
@@ -417,7 +419,7 @@ let ``ObjectListFilter works with OR operator``() =
417419

418420
[<Fact>]
419421
let ``ObjectListFilter works with NOT operator``() =
420-
let filter =
422+
let filter =
421423
Not (Equals { FieldName = "lastName"; Value = "Adams" })
422424
let queryable = data.AsQueryable()
423425
let filteredData = filter.Apply(queryable) |> Seq.toList
@@ -429,3 +431,140 @@ let ``ObjectListFilter works with NOT operator``() =
429431
result1.Contact |> equals { Email = "[email protected]" }
430432
result1.Friends |> equals []
431433

434+
type Complex =
435+
{ ID : int
436+
Name : string
437+
Discriminator : string }
438+
439+
type Building =
440+
{ ID : int
441+
Name : string
442+
Discriminator : string }
443+
444+
type Community =
445+
{ ID : int
446+
Name : string
447+
Discriminator : string
448+
Complexes : int list
449+
Buildings : int list }
450+
451+
type Property =
452+
| Complex of Complex
453+
| Building of Building
454+
| Community of Community
455+
456+
[<Fact>]
457+
let ``ObjectListFilter works with getDiscriminator for Complex``() =
458+
let propertyData: Property list =
459+
[
460+
Complex { ID = 1; Name = "Complex A"; Discriminator = typeof<Complex>.FullName}
461+
Building { ID = 2; Name = "Building B"; Discriminator = typeof<Building>.FullName }
462+
Community { ID = 3; Name = "Community C"; Discriminator = typeof<Community>.FullName; Complexes = [1]; Buildings = [2] }
463+
Complex { ID = 4; Name = "Complex AA"; Discriminator = typeof<Complex>.FullName }
464+
Building { ID = 5; Name = "Building BB"; Discriminator = typeof<Building>.FullName }
465+
Community { ID = 6; Name = "Community CC"; Discriminator = typeof<Community>.FullName; Complexes = [4]; Buildings = [5] }
466+
]
467+
let queryable = propertyData.AsQueryable()
468+
let filter = OfTypes [typeof<Complex>]
469+
let filteredData =
470+
filter.Apply(
471+
queryable,
472+
getDiscriminator =
473+
fun p ->
474+
match p with
475+
| Complex c -> c.Discriminator
476+
| Building b -> b.Discriminator
477+
| Community c -> c.Discriminator
478+
)
479+
|> Seq.toList
480+
List.length filteredData |> equals 2
481+
let result1 = List.head filteredData
482+
match result1 with
483+
| Complex c ->
484+
c.ID |> equals 1
485+
c.Name |> equals "Complex A"
486+
| _ -> failwith "Expected Complex"
487+
let result2 = List.last filteredData
488+
match result2 with
489+
| Complex c ->
490+
c.ID |> equals 4
491+
c.Name |> equals "Complex AA"
492+
| _ -> failwith "Expected Complex"
493+
494+
495+
[<Fact>]
496+
let ``ObjectListFilter works with getDiscriminator and getDiscriminatorValue for Complex``() =
497+
let propertyData: Property list =
498+
[
499+
Complex { ID = 1; Name = "Complex A"; Discriminator = "Complex" }
500+
Building { ID = 2; Name = "Building B"; Discriminator = "Building" }
501+
Community { ID = 3; Name = "Community C"; Discriminator = "Community"; Complexes = [1]; Buildings = [2] }
502+
Complex { ID = 4; Name = "Complex AA"; Discriminator = "Complex" }
503+
Building { ID = 5; Name = "Building BB"; Discriminator = "Building" }
504+
Community { ID = 6; Name = "Community CC"; Discriminator = "Community"; Complexes = [4]; Buildings = [5] }
505+
]
506+
let queryable = propertyData.AsQueryable()
507+
let filter = OfTypes [typeof<Complex>]
508+
let filteredData =
509+
filter.Apply(
510+
queryable,
511+
(fun p ->
512+
match p with
513+
| Complex c -> c.Discriminator
514+
| Building b -> b.Discriminator
515+
| Community c -> c.Discriminator),
516+
(function
517+
| t when t = typeof<Complex> -> "Complex"
518+
| t when t = typeof<Building> -> "Building"
519+
| t when t = typeof<Community> -> "Community"
520+
| _ -> raise (NotSupportedException "Type not supported"))
521+
)
522+
|> Seq.toList
523+
List.length filteredData |> equals 2
524+
let result1 = List.head filteredData
525+
match result1 with
526+
| Complex c ->
527+
c.ID |> equals 1
528+
c.Name |> equals "Complex A"
529+
| _ -> failwith "Expected Complex"
530+
let result2 = List.last filteredData
531+
match result2 with
532+
| Complex c ->
533+
c.ID |> equals 4
534+
c.Name |> equals "Complex AA"
535+
| _ -> failwith "Expected Complex"
536+
537+
538+
539+
[<Fact>]
540+
let ``ObjectListFilter works with getDiscriminatorValue for Complex``() =
541+
let propertyData: Property list =
542+
[
543+
Complex { ID = 1; Name = "Complex A"; Discriminator = typeof<Complex>.FullName}
544+
Building { ID = 2; Name = "Building B"; Discriminator = typeof<Building>.FullName }
545+
Community { ID = 3; Name = "Community C"; Discriminator = typeof<Community>.FullName; Complexes = [1]; Buildings = [2] }
546+
Complex { ID = 4; Name = "Complex AA"; Discriminator = typeof<Complex>.FullName }
547+
Building { ID = 5; Name = "Building BB"; Discriminator = typeof<Building>.FullName }
548+
Community { ID = 6; Name = "Community CC"; Discriminator = typeof<Community>.FullName; Complexes = [4]; Buildings = [5] }
549+
]
550+
let queryable = propertyData.AsQueryable()
551+
let filter = OfTypes [typeof<Complex>]
552+
let filteredData =
553+
filter.Apply(
554+
queryable,
555+
getDiscriminatorValue = (fun t -> t.FullName)
556+
)
557+
|> Seq.toList
558+
List.length filteredData |> equals 2
559+
let result1 = List.head filteredData
560+
match result1 with
561+
| Complex c ->
562+
c.ID |> equals 1
563+
c.Name |> equals "Complex A"
564+
| _ -> failwith "Expected Complex"
565+
let result2 = List.last filteredData
566+
match result2 with
567+
| Complex c ->
568+
c.ID |> equals 4
569+
c.Name |> equals "Complex AA"
570+
| _ -> failwith "Expected Complex"

0 commit comments

Comments
 (0)