./ct_report/coverage/mongoose_graphql.COVER.html

1 %% @doc This module provides main interface to graphql. It initializes schemas
2 %% and allows executing queries with permissions checks.
3 %% @end
4 -module(mongoose_graphql).
5
6 -include_lib("kernel/include/logger.hrl").
7
8 %API
9 -export([init/0,
10 get_endpoint/1,
11 create_endpoint/3,
12 execute/2, prepare/2,
13 execute/3,
14 execute_cli/3]).
15
16 -ignore_xref([create_endpoint/3]).
17
18 -type request() :: #{document := binary(),
19 operation_name := binary() | undefined,
20 vars := map(),
21 authorized := boolean(),
22 ctx := map(),
23 ast => graphql:ast()}.
24 -type context() :: map().
25 -type object() :: term().
26 -type field() :: binary().
27 -type args() :: map().
28
29 -type result() :: {ok, term()} | {ok, term(), Aux :: term()} | {error, term()}.
30 -callback execute(Ctx :: context(), Obj :: object(), Field :: field(), Args :: args()) ->
31 result().
32
33 -export_type([request/0, context/0, object/0, field/0, args/0]).
34
35 %% gen:start_ret() type is not exported from the gen module
36 -type gen_start_ret() :: {ok, pid()} | ignore | {error, term()}.
37
38 -define(USER_EP_NAME, user_schema_ep).
39 -define(ADMIN_EP_NAME, admin_schema_ep).
40
41 %% @doc Create and initialize endpoints for user and admin.
42 -spec init() -> ok.
43 init() ->
44 103 create_endpoint(?USER_EP_NAME, user_mapping_rules(), schema_global_patterns("user")),
45 103 create_endpoint(?ADMIN_EP_NAME, admin_mapping_rules(), schema_global_patterns("admin")),
46 103 ok.
47
48 %% @doc Get endpoint_context for passed endpoint name.
49 -spec get_endpoint(atom()) -> graphql:endpoint_context().
50 get_endpoint(admin) ->
51 1368 graphql_schema:get_endpoint_ctx(?ADMIN_EP_NAME);
52 get_endpoint(domain_admin) ->
53 468 graphql_schema:get_endpoint_ctx(?ADMIN_EP_NAME);
54 get_endpoint(user) ->
55 259 graphql_schema:get_endpoint_ctx(?USER_EP_NAME);
56 get_endpoint(Name) ->
57
:-(
graphql_schema:get_endpoint_ctx(Name).
58
59 %% @doc Create a new endpoint and load schema.
60 -spec create_endpoint(atom(), map(), [file:filename_all()]) -> gen_start_ret().
61 create_endpoint(Name, Mapping, Patterns) ->
62 206 Res = graphql_schema:start_link(Name),
63 206 Ep = graphql_schema:get_endpoint_ctx(Name),
64 206 {ok, SchemaData} = load_multiple_file_schema(Patterns),
65 206 ok = graphql:load_schema(Ep, Mapping, SchemaData),
66 206 ok = graphql:validate_schema(Ep),
67 206 Res.
68
69 %% @doc Execute request on a given endpoint.
70 -spec execute(graphql:endpoint_context(), request()) ->
71 {ok, map()} | {error, term()}.
72 execute(Ep, Req = #{ast := _}) ->
73 12 execute_graphql(Ep, Req);
74 execute(Ep, Req) ->
75 37378 case prepare(Ep, Req) of
76 {error, _} = Error ->
77 305 Error;
78 {ok, Req1} ->
79 37073 execute_graphql(Ep, Req1)
80 end.
81
82 -spec prepare(graphql:endpoint_context(), request()) -> {ok, request()} | {error, term()}.
83 prepare(Ep, Req) ->
84 37386 try {ok, prepare_request(Ep, Req)}
85 catch
86 throw:{error, Err} ->
87 309 {error, Err};
88 Class:Reason:Stacktrace ->
89
:-(
Err = #{what => graphql_internal_crash,
90 class => Class, reason => Reason,
91 stacktrace => Stacktrace},
92
:-(
?LOG_ERROR(Err),
93
:-(
{error, internal_crash}
94 end.
95
96 prepare_request(Ep, #{document := Doc,
97 operation_name := OpName,
98 authorized := AuthStatus,
99 vars := Vars,
100 ctx := Ctx} = Request) ->
101 37386 {ok, Ast} = graphql_parse(Doc),
102 37384 {ok, #{ast := Ast2,
103 fun_env := FunEnv}} = graphql:type_check(Ep, Ast),
104 37383 ok = graphql:validate(Ast2),
105 37383 Vars2 = remove_null_args(Vars),
106 37383 Coerced = graphql:type_check_params(Ep, FunEnv, OpName, Vars2),
107 37263 Ctx2 = Ctx#{params => Coerced,
108 operation_name => OpName,
109 authorized => AuthStatus,
110 error_module => mongoose_graphql_errors},
111 37263 Ast3 = mongoose_graphql_directive:process_directives(Ctx2, Ast2),
112 37079 mongoose_graphql_operations:verify_operations(Ctx2, Ast3),
113 37077 AllowedCategories = maps:get(allowed_categories, Ctx2, []),
114 37077 Ast4 = mongoose_graphql_check_categories:process_ast(Ast3, AllowedCategories),
115 37077 Request#{ast => Ast4, ctx := Ctx2}.
116
117 execute_graphql(Ep, #{ast := Ast, ctx := Ctx}) ->
118 37085 {ok, graphql:execute(Ep, Ctx, Ast)}.
119
120 %% @doc Execute selected operation on a given endpoint with authorization.
121 -spec execute(graphql:endpoint_context(), undefined | binary(), binary()) ->
122 {ok, map()} | {error, term()}.
123 execute(Ep, OpName, Doc) ->
124 555 Req = #{document => Doc,
125 operation_name => OpName,
126 vars => #{},
127 authorized => true,
128 ctx => #{}},
129 555 execute(Ep, Req).
130
131 -spec execute_cli(graphql:endpoint_context(), undefined | binary(), binary()) ->
132 {ok, map()} | {error, term()}.
133 execute_cli(Ep, OpName, Doc) ->
134 2 Req = #{document => Doc,
135 operation_name => OpName,
136 vars => #{},
137 authorized => true,
138 ctx => #{method => cli}},
139 2 execute(Ep, Req).
140
141 % Internal
142
143 -spec schema_global_patterns(file:name_all()) -> [file:filename_all()].
144 schema_global_patterns(SchemaDir) ->
145 206 [schema_pattern(SchemaDir), schema_pattern("global")].
146
147 -spec schema_pattern(file:name_all()) -> file:filename_all().
148 schema_pattern(DirName) ->
149 412 schema_pattern(DirName, "*.gql").
150
151 -spec schema_pattern(file:name_all(), file:name_all()) -> file:filename_all().
152 schema_pattern(DirName, Pattern) ->
153 412 filename:join([code:priv_dir(mongooseim), "graphql/schemas", DirName, Pattern]).
154
155 graphql_parse(Doc) ->
156 37386 case graphql:parse(Doc) of
157 {ok, _} = Ok ->
158 37384 Ok;
159 {error, Err} ->
160 2 graphql_err:abort([], parse, Err)
161 end.
162
163 remove_null_args(Vars) ->
164 37383 maps:filter(fun(_Key, Value) -> Value /= null end, Vars).
165
166 admin_mapping_rules() ->
167 103 #{objects => #{
168 'AdminQuery' => mongoose_graphql_admin_query,
169 'AdminMutation' => mongoose_graphql_admin_mutation,
170 'AdminSubscription' => mongoose_graphql_admin_subscription,
171 'AdminAuthInfo' => mongoose_graphql_admin_auth_info,
172 'DomainAdminQuery' => mongoose_graphql_domain_admin_query,
173 'GdprAdminQuery' => mongoose_graphql_gdpr_admin_query,
174 'DomainAdminMutation' => mongoose_graphql_domain_admin_mutation,
175 'InboxAdminMutation' => mongoose_graphql_inbox_admin_mutation,
176 'SessionAdminMutation' => mongoose_graphql_session_admin_mutation,
177 'SessionAdminQuery' => mongoose_graphql_session_admin_query,
178 'StanzaAdminMutation' => mongoose_graphql_stanza_admin_mutation,
179 'StatsAdminQuery' => mongoose_graphql_stats_admin_query,
180 'TokenAdminMutation' => mongoose_graphql_token_admin_mutation,
181 'GlobalStats' => mongoose_graphql_stats_global,
182 'DomainStats' => mongoose_graphql_stats_domain,
183 'StanzaAdminQuery' => mongoose_graphql_stanza_admin_query,
184 'StanzaAdminSubscription' => mongoose_graphql_stanza_admin_subscription,
185 'ServerAdminQuery' => mongoose_graphql_server_admin_query,
186 'ServerAdminMutation' => mongoose_graphql_server_admin_mutation,
187 'LastAdminMutation' => mongoose_graphql_last_admin_mutation,
188 'LastAdminQuery' => mongoose_graphql_last_admin_query,
189 'AccountAdminQuery' => mongoose_graphql_account_admin_query,
190 'AccountAdminMutation' => mongoose_graphql_account_admin_mutation,
191 'MUCAdminMutation' => mongoose_graphql_muc_admin_mutation,
192 'MUCAdminQuery' => mongoose_graphql_muc_admin_query,
193 'MUCLightAdminMutation' => mongoose_graphql_muc_light_admin_mutation,
194 'MUCLightAdminQuery' => mongoose_graphql_muc_light_admin_query,
195 'MnesiaAdminMutation' => mongoose_graphql_mnesia_admin_mutation,
196 'MnesiaAdminQuery' => mongoose_graphql_mnesia_admin_query,
197 'CETSAdminQuery' => mongoose_graphql_cets_admin_query,
198 'OfflineAdminMutation' => mongoose_graphql_offline_admin_mutation,
199 'PrivateAdminMutation' => mongoose_graphql_private_admin_mutation,
200 'PrivateAdminQuery' => mongoose_graphql_private_admin_query,
201 'RosterAdminQuery' => mongoose_graphql_roster_admin_query,
202 'VcardAdminMutation' => mongoose_graphql_vcard_admin_mutation,
203 'VcardAdminQuery' => mongoose_graphql_vcard_admin_query,
204 'HttpUploadAdminMutation' => mongoose_graphql_http_upload_admin_mutation,
205 'RosterAdminMutation' => mongoose_graphql_roster_admin_mutation,
206 'Domain' => mongoose_graphql_domain,
207 'MetricAdminQuery' => mongoose_graphql_metric_admin_query,
208 default => mongoose_graphql_default},
209 interfaces => #{default => mongoose_graphql_default},
210 scalars => #{default => mongoose_graphql_scalar},
211 enums => #{default => mongoose_graphql_enum},
212 unions => #{default => mongoose_graphql_union}}.
213
214 user_mapping_rules() ->
215 103 #{objects => #{
216 'UserQuery' => mongoose_graphql_user_query,
217 'UserMutation' => mongoose_graphql_user_mutation,
218 'UserSubscription' => mongoose_graphql_user_subscription,
219 'AccountUserQuery' => mongoose_graphql_account_user_query,
220 'AccountUserMutation' => mongoose_graphql_account_user_mutation,
221 'InboxUserMutation' => mongoose_graphql_inbox_user_mutation,
222 'MUCUserMutation' => mongoose_graphql_muc_user_mutation,
223 'MUCUserQuery' => mongoose_graphql_muc_user_query,
224 'MUCLightUserMutation' => mongoose_graphql_muc_light_user_mutation,
225 'MUCLightUserQuery' => mongoose_graphql_muc_light_user_query,
226 'PrivateUserMutation' => mongoose_graphql_private_user_mutation,
227 'PrivateUserQuery' => mongoose_graphql_private_user_query,
228 'RosterUserQuery' => mongoose_graphql_roster_user_query,
229 'RosterUserMutation' => mongoose_graphql_roster_user_mutation,
230 'VcardUserMutation' => mongoose_graphql_vcard_user_mutation,
231 'VcardUserQuery' => mongoose_graphql_vcard_user_query,
232 'LastUserMutation' => mongoose_graphql_last_user_mutation,
233 'LastUserQuery' => mongoose_graphql_last_user_query,
234 'SessionUserQuery' => mongoose_graphql_session_user_query,
235 'StanzaUserMutation' => mongoose_graphql_stanza_user_mutation,
236 'TokenUserMutation' => mongoose_graphql_token_user_mutation,
237 'StanzaUserQuery' => mongoose_graphql_stanza_user_query,
238 'StanzaUserSubscription' => mongoose_graphql_stanza_user_subscription,
239 'HttpUploadUserMutation' => mongoose_graphql_http_upload_user_mutation,
240 'UserAuthInfo' => mongoose_graphql_user_auth_info,
241 default => mongoose_graphql_default},
242 interfaces => #{default => mongoose_graphql_default},
243 scalars => #{default => mongoose_graphql_scalar},
244 enums => #{default => mongoose_graphql_enum},
245 unions => #{default => mongoose_graphql_union}}.
246
247 load_multiple_file_schema(Patterns) ->
248 206 Paths = lists:flatmap(fun(P) -> filelib:wildcard(P) end, Patterns),
249 206 try
250 206 SchemaData = [read_schema_file(P) || P <- Paths],
251 206 {ok, lists:flatten(SchemaData)}
252 catch
253 throw:{error, Reason, Path} ->
254
:-(
?LOG_ERROR(#{what => graphql_cannot_load_schema,
255
:-(
reason => Reason, path => Path}),
256
:-(
{error, cannot_load}
257 end.
258
259 read_schema_file(Path) ->
260 6798 case file:read_file(Path) of
261 {ok, Data} ->
262 6798 binary_to_list(Data);
263 {error, Reason} ->
264
:-(
throw({error, Reason, Path})
265 end.
Line Hits Source