./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,
13 execute/3]).
14
15 -ignore_xref([create_endpoint/3]).
16
17 -type request() :: #{document := binary(),
18 operation_name := binary() | undefined,
19 vars := map(),
20 authorized := boolean(),
21 ctx := map()}.
22 -type context() :: map().
23 -type object() :: term().
24 -type field() :: binary().
25 -type args() :: map().
26
27 -type result() :: {ok, term()} | {error, term()}.
28 -callback execute(Ctx :: context(), Obj :: object(), Field :: field(), Args :: args()) ->
29 result().
30
31 -export_type([request/0, context/0, object/0, field/0, args/0]).
32
33 -define(USER_EP_NAME, user_schema_ep).
34 -define(ADMIN_EP_NAME, admin_schema_ep).
35
36 %% @doc Create and initialize endpoints for user and admin.
37 -spec init() -> ok.
38 init() ->
39 83 create_endpoint(?USER_EP_NAME, user_mapping_rules(), schema_global_patterns("user")),
40 83 create_endpoint(?ADMIN_EP_NAME, admin_mapping_rules(), schema_global_patterns("admin")),
41 83 ok.
42
43 %% @doc Get endpoint_context for passed endpoint name.
44 -spec get_endpoint(atom()) -> graphql:endpoint_context().
45 get_endpoint(admin) ->
46 269 graphql_schema:get_endpoint_ctx(?ADMIN_EP_NAME);
47 get_endpoint(domain_admin) ->
48
:-(
graphql_schema:get_endpoint_ctx(?ADMIN_EP_NAME);
49 get_endpoint(user) ->
50 169 graphql_schema:get_endpoint_ctx(?USER_EP_NAME);
51 get_endpoint(Name) ->
52
:-(
graphql_schema:get_endpoint_ctx(Name).
53
54 %% @doc Create a new endpoint and load schema.
55 -spec create_endpoint(atom(), map(), [file:filename_all()]) -> gen:start_ret().
56 create_endpoint(Name, Mapping, Patterns) ->
57 166 Res = graphql_schema:start_link(Name),
58 166 Ep = graphql_schema:get_endpoint_ctx(Name),
59 166 {ok, SchemaData} = load_multiple_file_schema(Patterns),
60 166 ok = graphql:load_schema(Ep, Mapping, SchemaData),
61 166 ok = graphql:validate_schema(Ep),
62 166 Res.
63
64 %% @doc Execute request on a given endpoint.
65 -spec execute(graphql:endpoint_context(), request()) ->
66 {ok, map()} | {error, term()}.
67 execute(Ep, #{document := Doc,
68 operation_name := OpName,
69 authorized := AuthStatus,
70 vars := Vars,
71 ctx := Ctx}) ->
72 438 try
73 438 {ok, Ast} = graphql_parse(Doc),
74 437 {ok, #{ast := Ast2,
75 fun_env := FunEnv}} = graphql:type_check(Ep, Ast),
76 437 ok = graphql:validate(Ast2),
77 437 Coerced = graphql:type_check_params(Ep, FunEnv, OpName, Vars),
78 429 Ctx2 = Ctx#{params => Coerced,
79 operation_name => OpName,
80 authorized => AuthStatus,
81 error_module => mongoose_graphql_errors},
82 429 ok = mongoose_graphql_permissions:check_permissions(Ctx2, Ast2),
83 429 {ok, graphql:execute(Ep, Ctx2, Ast2)}
84 catch
85 throw:{error, Err} ->
86 9 {error, Err};
87 Class:Reason:Stacktrace ->
88
:-(
Err = #{what => graphql_internal_crash,
89 class => Class, reason => Reason,
90 stacktrace => Stacktrace},
91
:-(
?LOG_ERROR(Err),
92
:-(
{error, internal_crash}
93 end.
94
95 %% @doc Execute selected operation on a given endpoint with authorization.
96 -spec execute(graphql:endpoint_context(), undefined | binary(), binary()) ->
97 {ok, map()} | {error, term()}.
98 execute(Ep, OpName, Doc) ->
99 2 Req = #{document => Doc,
100 operation_name => OpName,
101 vars => #{},
102 authorized => true,
103 ctx => #{}},
104 2 execute(Ep, Req).
105
106 % Internal
107
108 -spec schema_global_patterns(file:name_all()) -> [file:filename_all()].
109 schema_global_patterns(SchemaDir) ->
110 166 [schema_pattern(SchemaDir), schema_pattern("global")].
111
112 -spec schema_pattern(file:name_all()) -> file:filename_all().
113 schema_pattern(DirName) ->
114 332 schema_pattern(DirName, "*.gql").
115
116 -spec schema_pattern(file:name_all(), file:name_all()) -> file:filename_all().
117 schema_pattern(DirName, Pattern) ->
118 332 filename:join([code:priv_dir(mongooseim), "graphql/schemas", DirName, Pattern]).
119
120 graphql_parse(Doc) ->
121 438 case graphql:parse(Doc) of
122 {ok, _} = Ok ->
123 437 Ok;
124 {error, Err} ->
125 1 graphql_err:abort([], parse, Err)
126 end.
127
128 admin_mapping_rules() ->
129 83 #{objects => #{
130 'AdminQuery' => mongoose_graphql_admin_query,
131 'AdminAuthInfo' => mongoose_graphql_admin_auth_info,
132 'DomainAdminQuery' => mongoose_graphql_domain_admin_query,
133 'AdminMutation' => mongoose_graphql_admin_mutation,
134 'DomainAdminMutation' => mongoose_graphql_domain_admin_mutation,
135 'SessionAdminMutation' => mongoose_graphql_session_admin_mutation,
136 'SessionAdminQuery' => mongoose_graphql_session_admin_query,
137 'StanzaAdminMutation' => mongoose_graphql_stanza_admin_mutation,
138 'StanzaAdminQuery' => mongoose_graphql_stanza_admin_query,
139 'LastAdminMutation' => mongoose_graphql_last_admin_mutation,
140 'LastAdminQuery' => mongoose_graphql_last_admin_query,
141 'AccountAdminQuery' => mongoose_graphql_account_admin_query,
142 'AccountAdminMutation' => mongoose_graphql_account_admin_mutation,
143 'MUCAdminMutation' => mongoose_graphql_muc_admin_mutation,
144 'MUCAdminQuery' => mongoose_graphql_muc_admin_query,
145 'MUCLightAdminMutation' => mongoose_graphql_muc_light_admin_mutation,
146 'MUCLightAdminQuery' => mongoose_graphql_muc_light_admin_query,
147 'OfflineAdminMutation' => mongoose_graphql_offline_admin_mutation,
148 'PrivateAdminMutation' => mongoose_graphql_private_admin_mutation,
149 'PrivateAdminQuery' => mongoose_graphql_private_admin_query,
150 'RosterAdminQuery' => mongoose_graphql_roster_admin_query,
151 'VcardAdminMutation' => mongoose_graphql_vcard_admin_mutation,
152 'VcardAdminQuery' => mongoose_graphql_vcard_admin_query,
153 'HttpUploadAdminMutation' => mongoose_graphql_http_upload_admin_mutation,
154 'RosterAdminMutation' => mongoose_graphql_roster_admin_mutation,
155 'Domain' => mongoose_graphql_domain,
156 'MetricAdminQuery' => mongoose_graphql_metric_admin_query,
157 default => mongoose_graphql_default},
158 interfaces => #{default => mongoose_graphql_default},
159 scalars => #{default => mongoose_graphql_scalar},
160 enums => #{default => mongoose_graphql_enum},
161 unions => #{default => mongoose_graphql_union}}.
162
163 user_mapping_rules() ->
164 83 #{objects => #{
165 'UserQuery' => mongoose_graphql_user_query,
166 'UserMutation' => mongoose_graphql_user_mutation,
167 'AccountUserQuery' => mongoose_graphql_account_user_query,
168 'AccountUserMutation' => mongoose_graphql_account_user_mutation,
169 'MUCUserMutation' => mongoose_graphql_muc_user_mutation,
170 'MUCUserQuery' => mongoose_graphql_muc_user_query,
171 'MUCLightUserMutation' => mongoose_graphql_muc_light_user_mutation,
172 'MUCLightUserQuery' => mongoose_graphql_muc_light_user_query,
173 'PrivateUserMutation' => mongoose_graphql_private_user_mutation,
174 'PrivateUserQuery' => mongoose_graphql_private_user_query,
175 'RosterUserQuery' => mongoose_graphql_roster_user_query,
176 'RosterUserMutation' => mongoose_graphql_roster_user_mutation,
177 'VcardUserMutation' => mongoose_graphql_vcard_user_mutation,
178 'VcardUserQuery' => mongoose_graphql_vcard_user_query,
179 'LastUserMutation' => mongoose_graphql_last_user_mutation,
180 'LastUserQuery' => mongoose_graphql_last_user_query,
181 'SessionUserQuery' => mongoose_graphql_session_user_query,
182 'StanzaUserMutation' => mongoose_graphql_stanza_user_mutation,
183 'StanzaUserQuery' => mongoose_graphql_stanza_user_query,
184 'HttpUploadUserMutation' => mongoose_graphql_http_upload_user_mutation,
185 'UserAuthInfo' => mongoose_graphql_user_auth_info,
186 default => mongoose_graphql_default},
187 interfaces => #{default => mongoose_graphql_default},
188 scalars => #{default => mongoose_graphql_scalar},
189 enums => #{default => mongoose_graphql_enum},
190 unions => #{default => mongoose_graphql_union}}.
191
192 load_multiple_file_schema(Patterns) ->
193 166 Paths = lists:flatmap(fun(P) -> filelib:wildcard(P) end, Patterns),
194 166 try
195 166 SchemaData = [read_schema_file(P) || P <- Paths],
196 166 {ok, lists:flatten(SchemaData)}
197 catch
198 throw:{error, Reason, Path} ->
199
:-(
?LOG_ERROR(#{what => graphql_cannot_load_schema,
200
:-(
reason => Reason, path => Path}),
201
:-(
{error, cannot_load}
202 end.
203
204 read_schema_file(Path) ->
205 4233 case file:read_file(Path) of
206 {ok, Data} ->
207 4233 binary_to_list(Data);
208 {error, Reason} ->
209
:-(
throw({error, Reason, Path})
210 end.
Line Hits Source