Skip to content

Commit ebfa5c0

Browse files
authored
Merge pull request #52 from Horusiath/validation-tests
Object inferface validation
2 parents 15886a1 + 81dbdbc commit ebfa5c0

File tree

4 files changed

+119
-10
lines changed

4 files changed

+119
-10
lines changed

src/FSharp.Data.GraphQL/Schema.fs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ type Schema<'Root> (query: ObjectDef<'Root>, ?mutation: ObjectDef<'Root>, ?confi
178178
let possibleTypes =
179179
getPossibleTypes idef
180180
|> Array.map (fun tdef -> Map.find tdef.Name namedTypes)
181-
IntrospectionType.Interface(idef.Name, idef.Description, fields, possibleTypes )
181+
IntrospectionType.Interface(idef.Name, idef.Description, fields, possibleTypes)
182182

183183
let introspectSchema types : IntrospectionSchema =
184184
let inamed =
@@ -213,6 +213,9 @@ type Schema<'Root> (query: ObjectDef<'Root>, ?mutation: ObjectDef<'Root>, ?confi
213213
let introspected = introspectSchema typeMap
214214
do
215215
compileSchema getPossibleTypes typeMap
216+
match Validation.validate typeMap with
217+
| Validation.Success -> ()
218+
| Validation.Error errors -> raise (GraphQLException (System.String.Join("\n", errors)))
216219

217220
member this.AsyncExecute(ast: Document, ?data: 'Root, ?variables: Map<string, obj>, ?operationName: string): Async<IDictionary<string,obj>> =
218221
async {

src/FSharp.Data.GraphQL/TypeSystem.fs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,6 @@ and EnumDefinition<'Val> =
400400
|> Seq.ofArray
401401
|> Seq.cast<EnumVal>
402402
|> Seq.toArray
403-
404403
interface NamedDef with
405404
member x.Name = x.Name
406405

src/FSharp.Data.GraphQL/Validation.fs

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,57 @@ module FSharp.Data.GraphQL.Validation
55

66
open FSharp.Data.GraphQL.Types
77

8-
type ValidationErrorInfo = {
9-
ObjectName: string
10-
Description: string
11-
}
12-
138
type ValidationResult =
149
| Success
15-
| Error of ValidationErrorInfo list
10+
| Error of string list
11+
12+
let (@) res1 res2 =
13+
match res1, res2 with
14+
| Success, Success -> Success
15+
| Success, _ -> res2
16+
| _, Success -> res1
17+
| Error e1, Error e2 -> Error (e1 @ e2)
18+
19+
let validateImplements (objdef: ObjectDef) (idef: InterfaceDef) =
20+
let objectFields =
21+
objdef.Fields
22+
|> Array.map (fun f -> (f.Name, f))
23+
|> Map.ofArray
24+
let errors =
25+
idef.Fields
26+
|> Array.fold (fun acc f ->
27+
match Map.tryFind f.Name objectFields with
28+
| None -> (sprintf "'%s' field is defined by interface %s, but not implemented in object %s" f.Name idef.Name objdef.Name)::acc
29+
| Some objf when objf = f -> acc
30+
| Some _ -> (sprintf "'%s.%s' field signature does not match it's definition in interface %s" objdef.Name f.Name idef.Name)::acc) []
31+
match errors with
32+
| [] -> Success
33+
| err -> Error err
34+
35+
let validateType (namedTypes: Map<string, NamedDef>) typedef =
36+
match typedef with
37+
| Scalar scalardef -> Success
38+
| Object objdef ->
39+
let nonEmptyResult = if objdef.Fields.Length > 0 then Success else Error [ objdef.Name + " must have at least one field defined" ]
40+
let implementsResult =
41+
objdef.Implements
42+
|> Array.fold (fun acc i -> acc @ validateImplements objdef i) Success
43+
nonEmptyResult @ implementsResult
44+
| InputObject indef ->
45+
let nonEmptyResult = if indef.Fields.Length > 0 then Success else Error [ indef.Name + " must have at least one field defined" ]
46+
nonEmptyResult
47+
| Union uniondef ->
48+
let nonEmptyResult = if uniondef.Options.Length > 0 then Success else Error [ uniondef.Name + " must have at least one type definition option" ]
49+
nonEmptyResult
50+
| Enum enumdef ->
51+
let nonEmptyResult = if enumdef.Options.Length > 0 then Success else Error [ enumdef.Name + " must have at least one enum value defined" ]
52+
nonEmptyResult
53+
| Interface idef ->
54+
let nonEmptyResult = if idef.Fields.Length > 0 then Success else Error [ idef.Name + " must have at least one field defined" ]
55+
nonEmptyResult
1656

17-
let detectCyclesInFragment fragmentDefintion visited = false
57+
let validate (namedTypes: Map<string, NamedDef>) : ValidationResult =
58+
namedTypes
59+
|> Map.toArray
60+
|> Array.map snd
61+
|> Array.fold (fun acc namedDef -> acc @ validateType namedTypes namedDef) Success
Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,68 @@
11
/// The MIT License (MIT)
22
/// Copyright (c) 2016 Bazinga Technologies Inc
3-
43
module FSharp.Data.GraphQL.Tests.ValidationTests
54

5+
open FSharp.Data.GraphQL
6+
open FSharp.Data.GraphQL.Validation
7+
open FSharp.Data.GraphQL.Types
8+
open Xunit
9+
open FsCheck
10+
11+
type ITestInterface =
12+
interface
13+
abstract TestProperty : string
14+
abstract TestMethod : int -> string -> string
15+
end
16+
17+
type TestDataType =
18+
{ TestProperty : string }
19+
interface ITestInterface with
20+
member x.TestProperty = x.TestProperty
21+
member x.TestMethod i y = x.TestProperty + y + i.ToString()
22+
23+
let TestInterface =
24+
Define.Interface<ITestInterface>("TestInterface",
25+
[ Define.Field("property", String)
26+
Define.Field("method", String, "Test method",
27+
[ Define.Input("x", Int)
28+
Define.Input("y", String) ], (fun _ _ -> "")) ])
29+
30+
[<Fact>]
31+
let ``Validation should inform about not implemented fields``() =
32+
let TestData =
33+
Define.Object<TestDataType>
34+
(name = "TestData", fields = [ Define.Field("property", String, (fun _ d -> d.TestProperty)) ],
35+
interfaces = [ TestInterface ])
36+
let expected =
37+
Error [ "'method' field is defined by interface TestInterface, but not implemented in object TestData" ]
38+
let result = validateImplements TestData TestInterface
39+
equals expected result
40+
41+
[<Fact>]
42+
let ``Validation should inform about fields with not matching signatures``() =
43+
let TestData =
44+
Define.Object<TestDataType>
45+
(name = "TestData",
46+
fields = [ Define.Field("property", Int, (fun _ d -> 1))
47+
Define.Field("method", String, "Test method", [ Define.Input("x", Int) ], (fun _ _ -> "res")) ],
48+
interfaces = [ TestInterface ])
49+
50+
let expected =
51+
Error
52+
[ "'TestData.method' field signature does not match it's definition in interface TestInterface";
53+
"'TestData.property' field signature does not match it's definition in interface TestInterface" ]
54+
let result = validateImplements TestData TestInterface
55+
equals expected result
56+
57+
[<Fact>]
58+
let ``Validation should succeed if object implements interface correctly``() =
59+
let TestData =
60+
Define.Object<TestDataType>
61+
(name = "TestData",
62+
fields = [ Define.Field("property", String, (fun _ d -> d.TestProperty))
63+
Define.Field("method", String, "Test method", [ Define.Input("x", Int); Define.Input("y", String) ], (fun _ _ -> "res")) ],
64+
interfaces = [ TestInterface ])
65+
66+
let result = validateImplements TestData TestInterface
67+
equals Success result
68+

0 commit comments

Comments
 (0)