-
Notifications
You must be signed in to change notification settings - Fork 75
/
Copy pathTypeSystem.fs
2444 lines (2133 loc) · 92.5 KB
/
TypeSystem.fs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// The MIT License (MIT)
// Copyright (c) 2016 Bazinga Technologies Inc
namespace FSharp.Data.GraphQL.Types
open System
open System.Reflection
open System.Collections
open System.Collections.Generic
open System.Collections.Immutable
open System.Text.Json
open FSharp.Data.GraphQL
open FSharp.Data.GraphQL.Ast
open FSharp.Data.GraphQL.Extensions
open FSharp.Data.GraphQL.Validation
open FSharp.Quotations
open FSharp.Quotations.Patterns
open FSharp.Reflection
open FSharp.Linq.RuntimeHelpers
/// Enum describing parts of the GraphQL query document AST, where
/// related directive is valid to be used.
[<Flags>]
type DirectiveLocation =
| QUERY = 1
| MUTATION = 2
| SUBSCRIPTION = 4
| FIELD = 8
| FRAGMENT_DEFINITION = 16
| FRAGMENT_SPREAD = 32
| INLINE_FRAGMENT = 64
| SCHEMA = 128
| SCALAR = 256
| OBJECT = 512
| FIELD_DEFINITION = 1024
| ARGUMENT_DEFINITION = 2048
| INTERFACE = 4096
| UNION = 8192
| ENUM = 16384
| ENUM_VALUE = 32768
| INPUT_OBJECT = 65536
| INPUT_FIELD_DEFINITION = 131072
module Introspection =
/// Type kind. GraphQL type system puts all types into one of eight categories.
type TypeKind =
| SCALAR = 1
| OBJECT = 2
| INTERFACE = 3
| UNION = 4
| ENUM = 5
| INPUT_OBJECT = 6
| LIST = 7
| NON_NULL = 8
/// Introspection descriptor of a directive (i.e. @skip(if:...), @include(if:...) etc).
type IntrospectionDirective = {
/// Directive name.
Name : string
/// Description of a target directive.
Description : string option
/// Array of AST locations, where it's valid to place target directive.
Locations : DirectiveLocation[]
/// Array of arguments, current directive can be parametrized with.
Args : IntrospectionInputVal[]
}
/// Introspection descriptor of a GraphQL type defintion.
and IntrospectionType = {
/// Which kind category current type belongs to.
Kind : TypeKind
/// Type name. Must be unique in scope of the defined schema.
Name : string
/// Optional type description.
Description : string option
/// Array of field descriptors defined within current type.
/// Only present for Object and Interface types.
Fields : IntrospectionField[] option
/// Array of interfaces implemented by output object type defintion.
Interfaces : IntrospectionTypeRef[] option
/// Array of type references being possible implementation of current type.
/// Only present for Union types (list of union cases) and Interface types
/// (list of all objects implementing interface in scope of the schema).
PossibleTypes : IntrospectionTypeRef[] option
/// Array of enum values defined by current Enum type.
EnumValues : IntrospectionEnumVal[] option
/// Array of input fields defined by current InputObject type.
InputFields : IntrospectionInputVal[] option
/// Type param reference - used only by List and NonNull types.
OfType : IntrospectionTypeRef option
} with
/// <summary>
/// Constructs an introspection descriptor for a <see cref="TypeKind.SCALAR"/> types.
/// </summary>
/// <param name="name">Type name (unique in the scope of current schema).</param>
/// <param name="description">Optional type description.</param>
static member Scalar (name : string, description : string option) = {
Kind = TypeKind.SCALAR
Name = name
Description = description
Fields = None
Interfaces = None
PossibleTypes = None
EnumValues = None
InputFields = None
OfType = None
}
/// <summary>
/// Constructs an introspection descriptor for a <see cref="TypeKind.OBJECT"/> types.
/// </summary>
/// <param name="name">Type name (unique in the scope of current schema).</param>
/// <param name="description">Optional type description.</param>
/// <param name="fields">Array of fields defined in current object.</param>
/// <param name="interfaces">Array of interfaces, current object implements.</param>
static member Object (name : string, description : string option, fields : IntrospectionField[], interfaces : IntrospectionTypeRef[]) = {
Kind = TypeKind.OBJECT
Name = name
Description = description
Fields = Some fields
Interfaces = Some interfaces
PossibleTypes = None
EnumValues = None
InputFields = None
OfType = None
}
/// <summary>
/// Constructs an introspection descriptor for a <see cref="TypeKind.INPUT_OBJECT"/> types.
/// </summary>
/// <param name="name">Type name (unique in the scope of current schema).</param>
/// <param name="description">Optional type description.</param>
/// <param name="inputFields">Array of input fields defined in current input object.</param>
static member InputObject (name : string, description : string option, inputFields : IntrospectionInputVal[]) = {
Kind = TypeKind.INPUT_OBJECT
Name = name
Description = description
Fields = None
Interfaces = None
PossibleTypes = None
EnumValues = None
InputFields = Some inputFields
OfType = None
}
/// <summary>
/// Constructs an introspection descriptor for a <see cref="TypeKind.UNION"/> types.
/// </summary>
/// <param name="name">Type name (unique in the scope of current schema).</param>
/// <param name="description">Optional type description.</param>
/// <param name="possibleTypes">Array of union case types. They can be any type defined in GraphQL schema.</param>
static member Union (name : string, description : string option, possibleTypes : IntrospectionTypeRef[]) = {
Kind = TypeKind.UNION
Name = name
Description = description
Fields = None
Interfaces = None
PossibleTypes = Some possibleTypes
EnumValues = None
InputFields = None
OfType = None
}
/// <summary>
/// Constructs an introspection descriptor for a <see cref="TypeKind.ENUM"/> types.
/// </summary>
/// <param name="name">Type name (unique in the scope of current schema).</param>
/// <param name="description">Optional type description.</param>
/// <param name="enumValues">Array of enum value descriptors.</param>
static member Enum (name : string, description : string option, enumValues : IntrospectionEnumVal[]) = {
Kind = TypeKind.ENUM
Name = name
Description = description
Fields = None
Interfaces = None
PossibleTypes = None
EnumValues = Some enumValues
InputFields = None
OfType = None
}
/// <summary>
/// Constructs an introspection descriptor for a <see cref="TypeKind.INTERFACE"/> types.
/// </summary>
/// <param name="name">Type name (unique in the scope of current schema).</param>
/// <param name="description">Optional type description.</param>
/// <param name="fields">Array of fields being part of the interface contract.</param>
/// <param name="possibleTypes">Array of schema objects implementing target interface.</param>
static member Interface (name : string, description : string option, fields : IntrospectionField[], possibleTypes : IntrospectionTypeRef[]) = {
Kind = TypeKind.INTERFACE
Name = name
Description = description
Fields = Some fields
Interfaces = None
PossibleTypes = Some possibleTypes
EnumValues = None
InputFields = None
OfType = None
}
/// Introspection type reference. Used to navigate between type dependencies inside introspected schema.
and IntrospectionTypeRef = {
/// Referenced type kind.
Kind : TypeKind
/// Type name. None if referenced type is List or NonNull.
Name : string option
/// Optional type description.
Description : string option
/// Type param reference. Used only by List and NonNull types.
OfType : IntrospectionTypeRef option
} with
/// <summary>
/// Constructs an introspection type reference for List types.
/// </summary>
/// <param name="inner">Type reference for type used as List's type param.</param>
static member List (inner : IntrospectionTypeRef) = { Kind = TypeKind.LIST; Name = None; Description = None; OfType = Some inner }
/// <summary>
/// Constructs an introspection type reference for NonNull types.
/// </summary>
/// <param name="inner">Type reference for type used as NonNull's type param.</param>
static member NonNull (inner : IntrospectionTypeRef) = {
Kind = TypeKind.NON_NULL
Name = None
Description = None
OfType = Some inner
}
/// <summary>
/// Constructs an introspection type reference for any named type defintion
/// (any type other than List or NonNull) with unique name included.
/// </summary>
/// <param name="inner">Introspection type descriptor to construct reference from.</param>
static member Named (inner : IntrospectionType) = {
Kind = inner.Kind
Name = Some inner.Name
Description = inner.Description
OfType = None
}
/// Introspection descriptor for input values (InputObject fields or field arguments).
and IntrospectionInputVal = {
/// Input argument name.
Name : string
/// Optional input argument description.
Description : string option
/// Introspection reference to argument's type.
Type : IntrospectionTypeRef
/// Default arguments value, if provided.
DefaultValue : string option
}
/// Introspection descriptor for enum values.
and IntrospectionEnumVal = {
/// Enum value name - must be unique in scope of defining enum.
Name : string
/// Optional enum value description.
Description : string option
/// If true, marks current value as deprecated, but still
/// available for compatibility reasons.
IsDeprecated : bool
/// If value is deprecated this field may describe a deprecation reason.
DeprecationReason : string option
}
/// Introspection descriptor for Object and Interface fields.
and IntrospectionField = {
/// Field name. Must be unique in scope of the definin object/interface.
Name : string
/// Optional field description.
Description : string option
/// Array of field arguments. In GraphQL fields can be parametrized,
/// working effectively like methods.
Args : IntrospectionInputVal[]
/// Introspection reference to field's type.
Type : IntrospectionTypeRef
/// If true, marks current field as deprecated, but still
/// available for compatibility reasons.
IsDeprecated : bool
/// If field is deprecated here a deprecation reason may be set.
DeprecationReason : string option
}
/// Introspection descriptor for target schema. Contains informations about
/// all types defined within current schema.
and IntrospectionSchema = {
/// Introspection reference to schema's query root.
QueryType : IntrospectionTypeRef
/// Introspection reference to schema's mutation root.
MutationType : IntrospectionTypeRef option
/// Introspection reference to schema's subscription root.
SubscriptionType : IntrospectionTypeRef option
/// Array of all introspection types defined within current schema.
/// Includes types for queries, mutations and subscriptions.
Types : IntrospectionType array
/// Array of all directives supported by current schema.
Directives : IntrospectionDirective array
}
/// Represents a subscription as described in the schema.
type Subscription = {
/// The name of the subscription type in the schema.
Name : string
/// Filter function, used to determine what events we will propagate.
/// The first object is the boxed root value, the second is the boxed value of the input object.
Filter : ResolveFieldContext -> obj -> obj -> Async<obj option>
}
/// Describes the backing implementation for a subscription system.
and ISubscriptionProvider =
interface
/// Registers a new subscription type, called at schema compilation time.
abstract member AsyncRegister : Subscription -> Async<unit>
/// Creates an active subscription, and returns the IObservable stream of POCO objects that will be projected on.
abstract member Add : ResolveFieldContext -> obj -> SubscriptionFieldDef -> IObservable<obj>
/// Publishes an event to the subscription system given the identifier of the subscription type.
abstract member AsyncPublish<'T> : string -> 'T -> Async<unit>
/// Publishes an event to the subscription system given the identifier of the subscription type
/// and a filter identity that can be used to choose which filter functions will be applied.
abstract member AsyncPublishTag<'T> : string -> Tag -> 'T -> Async<unit>
end
/// Represents a subscription of a field in a live query.
and ILiveFieldSubscription =
interface
/// Determine if we should propagate the event
abstract member Filter : obj -> obj -> bool
/// Project out the field marked with the @live directive
abstract member Project : obj -> obj
/// The type name of the object that is ready for live query in this subscription.
abstract member TypeName : string
/// The field name of the object that is ready for live query in this subscription.
abstract member FieldName : string
end
/// Represents a generic typed, subscription field in a live query.
and ILiveFieldSubscription<'Object, 'Field> =
interface
inherit ILiveFieldSubscription
/// Determine if we should propagate the event
abstract member Filter : 'Object -> 'Object -> bool
/// Project out the field marked with the @live directive
abstract member Project : 'Object -> 'Field
end
/// Represents a subscription of a field in a live query.
and LiveFieldSubscription = {
/// Determine if we should propagate the event
Filter : obj -> obj -> bool
/// Project out the field marked with the @live directive
Project : obj -> obj
/// The type name of the object that is ready for live query in this subscription.
TypeName : string
/// The field name of the object that is ready for live query in this subscription.
FieldName : string
} with
interface ILiveFieldSubscription with
member this.Filter x y = this.Filter x y
member this.Project x = this.Project x
member this.TypeName = this.TypeName
member this.FieldName = this.FieldName
/// Represents a generic typed, subscription field in a live query.
and LiveFieldSubscription<'Object, 'Field> = {
/// Determine if we should propagate the event
Filter : 'Object -> 'Object -> bool
/// Project out the field marked with the @live directive
Project : 'Object -> 'Field
/// The type name of the object that is ready for live query in this subscription.
TypeName : string
/// The field name of the object that is ready for live query in this subscription.
FieldName : string
} with
interface ILiveFieldSubscription<'Object, 'Field> with
member this.Filter x y = this.Filter x y
member this.Project x = this.Project x
interface ILiveFieldSubscription with
member this.Filter x y = this.Filter (downcast x) (downcast y)
member this.Project x = upcast this.Project (downcast x)
member this.TypeName = this.TypeName
member this.FieldName = this.FieldName
/// Describes the backing implementation of a live query subscription system.
and ILiveFieldSubscriptionProvider =
interface
/// Checks if a live field subscription has subscribers.
abstract member HasSubscribers : string -> string -> bool
/// Checks if a type and a field is registered in the provider.
abstract member IsRegistered : string -> string -> bool
/// Registers a new live query subscription type, called at schema compilation time.
abstract member AsyncRegister : ILiveFieldSubscription -> Async<unit>
/// Tries to find a subscription based on the type name and field name.
abstract member TryFind : string -> string -> ILiveFieldSubscription option
/// Creates an active subscription, and returns the IObservable stream of projected POCO objects
abstract member Add : (obj -> bool) -> string -> string -> IObservable<obj>
/// Publishes an event to the subscription system, given the key of the subscription type.
abstract member AsyncPublish<'T> : string -> string -> 'T -> Async<unit>
end
/// Interface used for receiving information about a whole
/// schema and type system defined within it.
and ISchema =
interface
inherit seq<NamedDef>
/// Map of defined types by their names.
abstract TypeMap : TypeMap
/// A query root object. Defines all top level fields,
/// that can be accessed from GraphQL queries.
abstract Query : ObjectDef
/// A mutation root object. Defines all top level operations,
/// that can be performed from GraphQL mutations.
abstract Mutation : ObjectDef option
// A subscription root object. Defines all top level operations,
// that can be performed from GraphQL subscriptions.
abstract Subscription : SubscriptionObjectDef option
/// List of all directives supported by the current schema.
abstract Directives : DirectiveDef[]
/// Method which, given type name, returns Some if provided
/// type has been defined in current schema. Otherwise None.
abstract TryFindType : string -> NamedDef option
/// Returns array of all possible types for provided abstract
/// type. For Union types, it's the array of all union options.
/// For Interface types, it's an array of all types - within
/// schema - implementing target interface.
abstract GetPossibleTypes : TypeDef -> ObjectDef[]
/// Checks if provided object is a possible type type (case
/// for Unions and implementation for Interfaces) of provided
/// abstract type.
abstract IsPossibleType : AbstractDef -> ObjectDef -> bool
/// Returns an introspected representation of current schema.
abstract Introspected : Introspection.IntrospectionSchema
/// Returns a function called when errors occurred during query execution.
/// It's used to retrieve messages shown as output to the client.
/// May be also used to log messages before returning them.
abstract ParseError : FieldPath -> exn -> IGQLError list
/// Returns the subscription provider implementation for this schema.
abstract SubscriptionProvider : ISubscriptionProvider
/// Returns the live query subscription provider implementation for this schema.
abstract LiveFieldSubscriptionProvider : ILiveFieldSubscriptionProvider
end
and ISchema<'Root> =
interface
inherit ISchema
abstract Query : ObjectDef<'Root>
abstract Mutation : ObjectDef<'Root> option
abstract Subscription : SubscriptionObjectDef<'Root> option
end
/// A type alias for a field execute compiler function.
and FieldExecuteCompiler = FieldDef -> ExecuteField
/// A field execute map object.
/// Field execute maps are mutable objects built to compile fields at runtime.
and FieldExecuteMap (compiler : FieldExecuteCompiler) =
let map = new Dictionary<string * string, ExecuteField * InputFieldDef[]> ()
let getKey typeName fieldName =
if List.exists ((=) fieldName) [ "__schema"; "__type"; "__typename" ] then
"", fieldName
else
typeName, fieldName
/// <summary>
/// Sets an execute function for a field of a named type of the schema.
/// </summary>
/// <param name="typeName">The type name of the parent object that has the field that needs to be executed.</param>
/// <param name="def">The FieldDef that will have its execute function configured into the FieldExecuteMap.</param>
/// <param name="overwrite">
/// If set to true, and an exists an entry with the <paramref name="typeName"/> and the name of the FieldDef,
/// then it will be overwritten.
/// </param>
member _.SetExecute (typeName : string, def : FieldDef, ?overwrite : bool) =
let overwrite = defaultArg overwrite false
let key = typeName, def.Name
let compiled = compiler def
let args = def.Args
match map.ContainsKey (key), overwrite with
| true, true ->
map.Remove (key) |> ignore
map.Add (key, (compiled, args))
| false, _ -> map.Add (key, (compiled, args))
| _ -> ()
/// <summary>
/// Sets an execute function for a field of an unamed type in the schema.
/// </summary>
/// <param name="def">The FieldDef that will have its execute function configured into the FieldExecuteMap.</param>
/// <param name="overwrite">If set to true, and an exists an entry with the FieldDef name, then it will be overwritten.</param>
member this.SetExecute (def : FieldDef, ?overwrite : bool) =
let overwrite = defaultArg overwrite false
this.SetExecute ("", def, overwrite)
/// <summary>
/// Gets an ExecuteField based on the name of the type and the name of the field.
/// </summary>
/// <param name="typeName">The type name of the parent object that has the field that needs to be executed.</param>
/// <param name="fieldName">The field name of the object that has the field that needs to be executed.</param>
member _.GetExecute (typeName : string, fieldName : string) =
let key = getKey typeName fieldName
match map.TryGetValue key with
| true, mapv -> fst mapv
| false, _ ->
Unchecked.defaultof<ExecuteField>
/// <summary>
/// Gets the field arguments based on the name of the type and the name of the field.
/// </summary>
/// <param name="typeName">The type name of the parent object that has the field that needs to be executed.</param>
/// <param name="fieldName">The field name of the object that has the field that needs to be executed.</param>
member _.GetArgs (typeName : string, fieldName : string) =
let key = getKey typeName fieldName
if map.ContainsKey (key) then
snd map.[key]
else
Unchecked.defaultof<InputFieldDef[]>
interface IEnumerable<string * string * ExecuteField> with
member _.GetEnumerator () =
let seq =
map
|> Seq.map (fun kvp -> fst kvp.Key, snd kvp.Key, fst kvp.Value)
seq.GetEnumerator ()
interface IEnumerable with
member _.GetEnumerator () =
let seq = map |> Seq.map (fun kvp -> fst kvp.Value)
upcast seq.GetEnumerator ()
/// Root of GraphQL type system. All type definitions use TypeDef as
/// a common root.
and TypeDef =
interface
/// Return .NET CLR type associated with current type definition.
abstract Type : Type
/// INTERNAL API: creates a List definition of a current type.
abstract MakeList : unit -> ListOfDef
/// INTERNAL API: creates a Nullable definition of a current type.
abstract MakeNullable : unit -> NullableDef
end
/// Root of GraphQL type system. Constrained to represent .NET type
/// provided as generic parameter.
and TypeDef<'Val> =
interface
inherit TypeDef
end
/// Representation of all type defintions, that can be uses as inputs.
/// By default only scalars, enums, lists, nullables and input objects
/// are valid input types.
and InputDef =
interface
inherit TypeDef
end
/// Representation of all type defintions, that can be uses as inputs.
/// By default only scalars, enums, lists, nullables and input objects
/// are valid input types. Constrained to represent .NET type provided
/// as generic parameter.
and InputDef<'Val> =
interface
inherit InputDef
inherit TypeDef<'Val>
end
/// Representation of all type defintions, that can be uses as outputs.
/// By default only scalars, enums, lists, nullables, unions, interfaces
/// and objects are valid output types.
and OutputDef =
interface
inherit TypeDef
end
/// Representation of all type defintions, that can be uses as outputs.
/// By default only scalars, enums, lists, nullables, unions, interfaces
/// and objects are valid input types. Constrained to represent .NET type
/// provided as generic parameter.
and OutputDef<'Val> =
interface
inherit OutputDef
inherit TypeDef<'Val>
end
/// Representation of leaf type definitions. Leaf types represents leafs
/// of the GraphQL query tree. Each query path must end with a leaf.
/// By default only scalars and enums are valid leaf types.
and LeafDef =
interface
inherit TypeDef
end
/// Representation of composite types. Composites are non-leaf nodes of
/// the GraphQL query tree. Query path cannot end with a composite.
/// Composite type defines list of fields, it consists of. By default
/// only interfaces, unions and objects are valid composite types.
and CompositeDef =
interface
inherit TypeDef
end
/// Representation of abstract types: interfaces and unions. Each abstract
/// type contains a collection of possible object types, which can be resolved
/// from schema.
and AbstractDef =
interface
inherit TypeDef
//NOTE: only abstract types are Interface and Union, which are both composite defs too
inherit CompositeDef
end
/// Representation of named types. All named types are registered in
/// a schema. By default only non-named types are nullables and lists.
and NamedDef =
interface
inherit TypeDef
/// Returns a name of the current named type. It must be unique
/// in scope of the defining schema.
abstract Name : string
end
/// A context holding all the information needed for planning an operation.
and PlanningContext = {
Schema : ISchema
RootDef : ObjectDef
Document : Document
Operation : OperationDefinition
DocumentId : int
Metadata : Metadata
}
/// A function type, which upon execution returns true if related field should
/// be included in result set for the query.
and Includer = ImmutableDictionary<string, obj> -> Result<bool, IGQLError list>
/// A node representing part of the current GraphQL query execution plan.
/// It contains info about both document AST fragment of incoming query as well,
/// as field defintion and type info of related fields, defined in schema.
and ExecutionInfo = {
/// Field identifier, which may be either field name or alias. For top level execution plan it will be None.
Identifier : string
/// Field definition of corresponding type found in current schema.
Definition : FieldDef
/// AST node of the parsed query document.
Ast : Field
/// A type of execution plan.
Kind : ExecutionInfoKind
/// Logic describing, if correlated field should be included in result set.
Include : Includer
/// Composite definition being the parent of the current field, execution plan refers to.
ParentDef : OutputDef
/// Type definition marking returned type.
ReturnDef : OutputDef
/// Flag determining if flag allows to have nullable output.
IsNullable : bool
} with
/// Get a nested info recognized by path provided as parameter. Path may consist of fields names or aliases.
member this.GetPath (keys : string list) : ExecutionInfo option =
let rec path info segments =
match segments with
| [] ->
match info.Kind with
| ResolveCollection inner -> Some inner
| _ -> Some info
| head :: tail ->
match info.Kind with
| ResolveDeferred inner -> path inner segments
| ResolveLive inner -> path inner segments
| ResolveStreamed (inner, _) -> path inner segments
| ResolveValue -> None
| ResolveCollection inner -> path inner segments
| SelectFields fields ->
fields
|> List.tryFind (fun f -> f.Identifier = head)
|> Option.bind (fun f -> path f tail)
| ResolveAbstraction typeMap ->
typeMap
|> Map.toSeq
|> Seq.map snd
|> Seq.collect id
|> Seq.tryFind (fun f -> f.Identifier = head)
|> Option.bind (fun f -> path f tail)
path this keys
override this.ToString () =
let pad indent (sb : Text.StringBuilder) =
for i = 0 to indent do
sb.Append '\t' |> ignore
let nameAs info =
match info.Ast.Alias with
| ValueSome alias -> $"""{info.Ast.Name} as {alias} of {info.ReturnDef}{(if info.IsNullable then "" else "!")}"""
| ValueNone -> $"""{info.Ast.Name} of {info.ReturnDef}{(if info.IsNullable then "" else "!")}"""
let rec str indent sb info =
match info.Kind with
| ResolveValue ->
pad indent sb
sb.Append("ResolveValue: ").AppendLine (nameAs info)
|> ignore
| ResolveDeferred inner ->
pad indent sb
sb.Append("ResolveDeferred: ").AppendLine (nameAs info)
|> ignore
str (indent + 1) sb inner
| ResolveLive inner ->
pad indent sb
sb.Append("ResolveLive: ").AppendLine (nameAs info)
|> ignore
str (indent + 1) sb inner
| ResolveStreamed (inner, mode) ->
pad indent sb
sb.Append("ResolveStreamed: ").AppendLine (nameAs info)
|> ignore
str (indent + 1) sb inner
| SelectFields fields ->
pad indent sb
sb.Append("SelectFields: ").AppendLine (nameAs info)
|> ignore
fields |> List.iter (str (indent + 1) sb)
| ResolveCollection inner ->
pad indent sb
sb.Append("ResolveCollection: ").AppendLine (nameAs info)
|> ignore
str (indent + 1) sb inner
| ResolveAbstraction types ->
pad indent sb
sb.Append("ResolveAbstraction: ").AppendLine (nameAs info)
|> ignore
types
|> Map.iter (fun tname fields ->
pad (indent + 1) sb
sb.Append("Case: ").AppendLine (tname) |> ignore
fields |> List.iter (str (indent + 2) sb))
let sb = Text.StringBuilder ()
str 0 sb this
sb.ToString ()
/// Kind of ExecutionInfo, marking a reduction operations, that should be applied to it.
and ExecutionInfoKind =
/// Reduce scalar or enum to a returned value.
| ResolveValue
/// Reduce result set by selecting provided set of fields,
/// defined inside composite type, current execution info
/// refers to.
| SelectFields of fields : ExecutionInfo list
/// Reduce current field as a collection, applying provided
/// execution info on each of the collection's element.
| ResolveCollection of elementPlan : ExecutionInfo
/// Reduce union or interface types by applying provided set of
/// field infos depending on what concrete object implementation
/// will be found.
| ResolveAbstraction of typeFields : Map<string, ExecutionInfo list>
/// Reduce result set as a deferred result.
| ResolveDeferred of ExecutionInfo
/// Reduce the current field as a stream, applying
/// the provided execution info on each of the
/// collection's elements.
| ResolveStreamed of ExecutionInfo * BufferedStreamOptions
/// Reduce the current field as a live query.
| ResolveLive of ExecutionInfo
/// Buffered stream options. Used to specify how the buffer will behavior in a stream.
and BufferedStreamOptions = {
/// The maximum time in milliseconds that the buffer will be filled before being sent to the subscriber.
Interval : int option
/// The maximum number of items that will be buffered before being sent to the subscriber.
PreferredBatchSize : int option
}
/// Wrapper for a resolve method defined by the user or generated by a runtime.
and Resolve =
/// Resolve function hasn't been defined. Valid only for interface fields.
| Undefined
/// Resolve field value in synchronous way.
/// input defines .NET type of the provided object
/// output defines .NET type of the returned value
/// expr is untyped version of Expr<ResolveFieldContext->'Input->'Output>
| Sync of input : Type * output : Type * expr : Expr
/// Resolve field value as part of Async computation.
/// input defines .NET type of the provided object
/// output defines .NET type of the returned value
/// expr is untyped version of Expr<ResolveFieldContext->'Input->Async<'Output>>
| Async of input : Type * output : Type * expr : Expr
/// Resolves the filter function of a subscription.
/// root defines the .NET type of the root object
/// input defines the .NET type of the value being subscribed to
/// expr is the untyped version of Expr<ResolveFieldContext -> 'Root -> 'Input -> bool>
| Filter of root : Type * input : Type * output : Type * expr : Expr
/// Resolves the filter function of a subscription that has asyncronous fields.
/// root defines the .NET type of the root object
/// input defines the .NET type of the value being subscribed to
/// expr is the untyped version of Expr<ResolveFieldContext -> 'Root -> 'Input -> bool>
| AsyncFilter of root : Type * input : Type * output : Type * expr : Expr
| ResolveExpr of expr : Expr
/// Returns an expression defining resolver function.
member x.Expr =
match x with
| Sync (_, _, e) -> e
| Async (_, _, e) -> e
| ResolveExpr (e) -> e
| Undefined -> failwith "Resolve function was not defined"
| x -> failwith <| sprintf "Unexpected resolve function %A" x
/// Execution strategy for provided queries. Defines if object fields should
/// be resolved either sequentially one-by-one or in parallel.
and ExecutionStrategy =
/// Object fields will be resolved one-by-one. This is default option
/// for mutations.
| Sequential
/// Object fields will be reslved in parallel. It's only valid for
/// read-only operations like queries. It's not valid for mutations.
| Parallel
/// Type representing a variable definition inside GraphQL query.
and VarDef = {
/// Variable name without prefixed '$'.
Name : string
/// Type definition in corresponding GraphQL schema.
TypeDef : InputDef
/// Optional default value.
DefaultValue : InputValue option
}
/// The context used to hold all the information for a schema compiling proccess.
and SchemaCompileContext = { Schema : ISchema; TypeMap : TypeMap; FieldExecuteMap : FieldExecuteMap }
/// A planning of an execution phase.
/// It is used by the execution process to execute an operation.
and ExecutionPlan = {
/// Unique identifier of the current execution plan.
DocumentId : int
/// AST defintion of current operation.
Operation : OperationDefinition
/// Definition of the root type (either query or mutation) used by the
/// current operation.
RootDef : ObjectDef
/// Execution strategy applied on the underlying object's fields.
Strategy : ExecutionStrategy
/// List of fields of top level query/mutation object to be resolved.
Fields : ExecutionInfo list
/// List of variables defined within executed query.
Variables : VarDef list
/// A dictionary of metadata associated with custom operations on the planning of this plan.
Metadata : Metadata
} with
member x.Item
with get (id) = x.Fields |> List.find (fun f -> f.Identifier = id)
/// Execution context of the current GraphQL operation. It contains a full
/// knowledge about which fields will be accessed, what types are associated
/// with them and what variable values have been set by incoming query.
and ExecutionContext = {
/// GraphQL schema definition.
Schema : ISchema
/// Boxed value of the top level type, root query/mutation.
RootValue : obj
/// Execution plan describing, what fiedls are going to be resolved.
ExecutionPlan : ExecutionPlan
/// Collection of variables provided to execute current operation.
Variables : ImmutableDictionary<string, obj>
/// A map of all fields of the query and their respective execution operations.
FieldExecuteMap : FieldExecuteMap
/// A simple dictionary to hold metadata that can be used by execution customizations.
Metadata : Metadata
}
/// An execution context for the particular field, applied as the first
/// parameter for target resolve function.
and ResolveFieldContext = {
/// Fragment of the overall execution plan related to current field.
ExecutionInfo : ExecutionInfo
/// Current operation execution context.
Context : ExecutionContext
/// GraphQL type definition for the returned value.
ReturnType : TypeDef
/// GraphQL type definition for a parent object, current value needs
/// to be resolved from.
ParentType : ObjectDef
/// Current GraphQL schema.
Schema : ISchema
/// Untyped map of all argument values used for as current field's
/// parametrized inputs.
Args : Map<string, obj>
/// Variables provided by the operation caller.
Variables : ImmutableDictionary<string, obj>
/// Field path
Path : FieldPath
} with
/// Tries to find an argument by provided name.
member x.TryArg (name : string) : 't option =
match Map.tryFind name x.Args with
| Some o -> Some (o :?> 't) // TODO: Use Convert.ChangeType
| None -> None
/// Function type for the compiled field executor.
and ExecuteField = ResolveFieldContext -> obj -> AsyncVal<obj>
/// Untyped representation of the GraphQL field defintion.
/// Can be used only withing object and interface definitions.
and FieldDef =
interface
/// Name of the field.
abstract Name : string
/// Optional field description.
abstract Description : string option
/// Optional field deprecation warning.
abstract DeprecationReason : string option
/// Field's GraphQL type definition.
abstract TypeDef : OutputDef
/// Field's arguments list.
abstract Args : InputFieldDef[]
/// Field's metadata.
abstract Metadata : Metadata
/// Field resolution function.
abstract Resolve : Resolve
// INTERNAL API: Compiled field executor. To be set only by the runtime.
inherit IEquatable<FieldDef>
end
/// A paritally typed representation of the GraphQL field defintion.
/// Contains type parameter describing .NET type used as it's container.
/// Can be used only withing object and interface definitions.
and FieldDef<'Val> =
interface
inherit FieldDef
end
and FieldDef<'Val, 'Res> =
interface
inherit FieldDef<'Val>
end
and [<CustomEquality; NoComparison>] internal FieldDefinition<'Val, 'Res> =
{ /// Name of the field.
Name : string
/// Optional field description.
Description : string option
/// Field's GraphQL type definition.
TypeDef : OutputDef<'Res>
/// Field resolution function.
Resolve : Resolve
/// Field's arguments list.
Args : InputFieldDef []
/// Optional field deprecation warning.
DeprecationReason : string option
/// Field metadata definition.
Metadata : Metadata }
interface FieldDef with
member x.Name = x.Name
member x.Description = x.Description
member x.DeprecationReason = x.DeprecationReason
member x.TypeDef = x.TypeDef :> OutputDef
member x.Args = x.Args
member x.Resolve = x.Resolve
member x.Metadata = x.Metadata
interface FieldDef<'Val, 'Res>
interface IEquatable<FieldDef> with
member x.Equals f =
x.Name = f.Name
&& x.TypeDef :> OutputDef = f.TypeDef
&& x.Args = f.Args
override x.Equals y =
match y with