8
8
-compile (inline_list_funcs ).
9
9
-compile ({inline_size , 50 }).
10
10
11
- -export ([x /1 , x /2 ]).
11
+ -export ([x /1 , x /2 , x_subscription_event / 4 ]).
12
12
-export ([builtin_input_coercer /1 ]).
13
+ -export_type ([subscription_ctx / 0 ]).
14
+
13
15
-type source () :: reference ().
14
16
-type demonitor () :: {reference (), pid ()} .
15
17
74
76
work = #{} :: #{ source () => defer_closure () },
75
77
timeout :: non_neg_integer () }).
76
78
77
- -spec x (graphql :ast ()) -> #{ atom () => graphql :json () }.
79
+ -record (subscription ,
80
+ { orig_context ,
81
+ op ,
82
+ fragments }).
83
+
84
+ -opaque subscription_ctx () :: # subscription {}.
85
+
86
+ -spec x (graphql :ast ()) ->
87
+ graphql :execute_result ()
88
+ | {subscription , graphql :subscription_value (), subscription_ctx ()}.
78
89
x (X ) -> x (#{ params => #{} }, X ).
79
90
80
- -spec x (term (), graphql :ast ()) -> #{ atom () => graphql :json () }.
91
+ -spec x (term (), graphql :ast ()) ->
92
+ graphql :execute_result ()
93
+ | {subscription , graphql :subscription_value (), subscription_ctx ()}.
81
94
x (Ctx , X ) ->
82
95
Canon = canon_context (Ctx ),
83
96
execute_request (Canon , X ).
84
97
98
+ -spec x_subscription_event (map (), graphql :subscription_value (), subscription_ctx (), any ()) ->
99
+ #{atom () => graphql :json ()}.
100
+ x_subscription_event (ExtraCtx , Subscription ,
101
+ # subscription {orig_context = OrigContext ,
102
+ op = Op ,
103
+ fragments = Frags }, Event ) ->
104
+ CanonCtx = canon_context (maps :merge (OrigContext , ExtraCtx )),
105
+ Ctx = CanonCtx # ectx { frags = Frags ,
106
+ defer_request_id = make_ref () },
107
+ InitialValue = {Subscription , Event },
108
+ execute_query (Ctx # ectx { op_type = subscription }, Op , InitialValue ).
109
+
85
110
execute_request (InitialCtx , # document { definitions = Operations }) ->
86
111
{Frags , Ops } = lists :partition (fun (# frag {}) -> true ;(_ ) -> false end , Operations ),
87
112
Ctx = InitialCtx # ectx { frags = fragments (Frags ),
88
113
defer_request_id = make_ref () },
89
114
case get_operation (Ctx , Ops ) of
90
115
{ok , # op { ty = {query , _ } } = Op } ->
91
- execute_query (Ctx # ectx { op_type = query }, Op );
92
- {ok , # op { ty = {subscription , _ } } = Op } ->
93
- execute_query (Ctx # ectx { op_type = subscription }, Op );
116
+ execute_query (Ctx # ectx { op_type = query }, Op , none );
94
117
{ok , # op { ty = undefined } = Op } ->
95
- execute_query (Ctx # ectx { op_type = query }, Op );
118
+ execute_query (Ctx # ectx { op_type = query }, Op , none );
96
119
{ok , # op { ty = {mutation , _ } } = Op } ->
97
120
execute_mutation (Ctx # ectx { op_type = mutation }, Op );
121
+ {ok , # op { ty = {subscription , _ } } = Op } ->
122
+ create_source_event_stream (Ctx # ectx { op_type = subscription }, Op );
98
123
{error , Reason } ->
99
124
{error , Errs } = err (Ctx , Reason ),
100
125
complete_top_level (undefined , Errs )
@@ -124,12 +149,13 @@ collect_auxiliary_data() ->
124
149
end .
125
150
126
151
execute_query (# ectx { defer_request_id = ReqId } = Ctx ,
127
- # op { selection_set = SSet , schema = QType }) ->
152
+ # op { selection_set = SSet , schema = QType },
153
+ InitialValue ) ->
128
154
# object_type {} = QType ,
129
155
case execute_sset (Ctx # ectx { path = [],
130
156
defer_process = self (),
131
157
defer_target = top_level },
132
- SSet , QType , none ) of
158
+ SSet , QType , InitialValue ) of
133
159
{ok , Res , Errs } ->
134
160
complete_top_level (Res , Errs );
135
161
# work { items = WL , monitor = Ms , demonitors = [] , timeout = TimeOut } ->
@@ -154,6 +180,34 @@ execute_mutation(Ctx, #op { selection_set = SSet,
154
180
complete_top_level (Res , Errs )
155
181
end .
156
182
183
+ % % 6.2.3.1 https://spec.graphql.org/October2021/#sec-Source-Stream
184
+ create_source_event_stream (# ectx { frags = Frags , ctx = OrigCtx } = Ctx ,
185
+ # op { selection_set = SSet ,
186
+ schema = QType } = Op ) ->
187
+ # object_type {} = QType ,
188
+ GroupedFieldSet = collect_fields (Ctx , QType , SSet ),
189
+ case orddict :to_list (GroupedFieldSet ) of
190
+ [{Key , [Field ]}] ->
191
+ FieldName = name (Field ),
192
+ # schema_field { directives = Directives } = lookup_field (Field , QType ),
193
+ Args = resolve_args (Ctx , Field ),
194
+ Fun = subscribe_resolver_function (QType ),
195
+ CtxP = add_path (Ctx , Key ),
196
+ case resolve_field_event_stream (CtxP , QType , FieldName , Directives , Fun , Args ) of
197
+ {subscription , Sub } ->
198
+ SubCtx = # subscription {orig_context = OrigCtx ,
199
+ op = Op ,
200
+ fragments = Frags },
201
+ {subscription , Sub , SubCtx };
202
+ {error , Reason } ->
203
+ {error , Errs } = err (CtxP , Reason ),
204
+ complete_top_level (undefined , Errs )
205
+ end ;
206
+ _Other ->
207
+ {error , Errs } = err (Ctx , subscription_must_have_one_root_field ),
208
+ complete_top_level (undefined , Errs )
209
+ end .
210
+
157
211
execute_sset (# ectx { defer_target = DeferTarget } = Ctx , SSet , Type , Value ) ->
158
212
GroupedFields = collect_fields (Ctx , Type , SSet ),
159
213
Self = make_ref (),
@@ -557,6 +611,44 @@ resolve_field_value(#ectx { op_type = OpType,
557
611
{error , {resolver_crash , M }}
558
612
end .
559
613
614
+ resolve_field_event_stream (# ectx { op_type = OpType ,
615
+ ctx = CallerContext },
616
+ # object_type { id = OID ,
617
+ directives = ODirectives } = ObjectType ,
618
+ Name , FDirectives , Fun , Args ) ->
619
+ AnnotatedCallerCtx =
620
+ CallerContext #{ op_type => OpType ,
621
+ field => Name ,
622
+ field_directives => format_directives (FDirectives ),
623
+ object_type => OID ,
624
+ object_directives => format_directives (ODirectives )
625
+ },
626
+ try Fun (AnnotatedCallerCtx , Name , Args ) of
627
+ V ->
628
+ case handle_subscribe_resolver_result (V ) of
629
+ wrong ->
630
+ Obj = graphql_schema :id (ObjectType ),
631
+ report_wrong_return (Obj , Name , Fun , V ),
632
+ {error , {wrong_resolver_return , {Obj , Name }}};
633
+ Res -> Res
634
+ end
635
+ catch
636
+ throw :{'$graphql_throw' , Msg } ->
637
+ case handle_subscribe_resolver_result (Msg ) of
638
+ wrong ->
639
+ Obj = graphql_schema :id (ObjectType ),
640
+ report_wrong_return (Obj , Name , Fun , Msg ),
641
+ {error , {wrong_resolver_return , {Obj , Name }}};
642
+ Res -> Res
643
+ end ;
644
+ ? EXCEPTION (Cl , Err , Stacktrace ) ->
645
+ M = #{ type => graphql_schema :id (ObjectType ),
646
+ field => Name ,
647
+ stack => ? GET_STACK (Stacktrace ),
648
+ class => Cl ,
649
+ error => Err },
650
+ {error , {resolver_crash , M }}
651
+ end .
560
652
561
653
handle_resolver_result ({error , Reason }) ->
562
654
{error , {resolver_error , Reason }};
@@ -571,6 +663,12 @@ handle_resolver_result({defer, Token, DeferStateMap}) ->
571
663
{defer , Token , DeferStateMap };
572
664
handle_resolver_result (_Unknown ) -> wrong .
573
665
666
+ handle_subscribe_resolver_result ({subscription , V }) ->
667
+ {subscription , V };
668
+ handle_subscribe_resolver_result ({error , Reason }) ->
669
+ {error , {resolver_error , Reason }};
670
+ handle_subscribe_resolver_result (_Unknown ) -> wrong .
671
+
574
672
complete_value (Ctx , Ty , Fields , {ok , Value }) when is_binary (Ty ) ->
575
673
error_logger :warning_msg (
576
674
" Canary: Type lookup during value completion for: ~p~n " ,
@@ -929,6 +1027,9 @@ resolver_function(#object_type {
929
1027
resolver_function (# object_type { resolve_module = M }, undefined ) ->
930
1028
fun M :execute /4 .
931
1029
1030
+ subscribe_resolver_function (# object_type { resolve_module = M }) ->
1031
+ fun M :subscribe /3 .
1032
+
932
1033
% % -- OUTPUT COERCION ------------------------------------
933
1034
934
1035
builtin_input_coercer (X ) ->
0 commit comments