./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 76 create_endpoint(?USER_EP_NAME, user_mapping_rules(), schema_global_patterns("user")),
40 76 create_endpoint(?ADMIN_EP_NAME, admin_mapping_rules(), schema_global_patterns("admin")),
41 76 ok.
42
43 %% @doc Get endpoint_context for passed endpoint name.
44 -spec get_endpoint(atom()) -> graphql:endpoint_context().
45 get_endpoint(admin) ->
46 230 graphql_schema:get_endpoint_ctx(?ADMIN_EP_NAME);
47 get_endpoint(domain_admin) ->
48 2 graphql_schema:get_endpoint_ctx(?ADMIN_EP_NAME);
49 get_endpoint(user) ->
50 154 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 152 Res = graphql_schema:start_link(Name),
58 152 Ep = graphql_schema:get_endpoint_ctx(Name),
59 152 {ok, SchemaData} = load_multiple_file_schema(Patterns),
60 152 ok = graphql:load_schema(Ep, Mapping, SchemaData),
61 152 ok = graphql:validate_schema(Ep),
62 152 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 386 try
73 386 {ok, Ast} = graphql_parse(Doc),
74 385 {ok, #{ast := Ast2,
75 fun_env := FunEnv}} = graphql:type_check(Ep, Ast),
76 385 ok = graphql:validate(Ast2),
77 385 Coerced = graphql:type_check_params(Ep, FunEnv, OpName, Vars),
78 377 Ctx2 = Ctx#{params => Coerced,
79 operation_name => OpName,
80 authorized => AuthStatus,
81 error_module => mongoose_graphql_errors},
82 377 ok = mongoose_graphql_permissions:check_permissions(Ctx2, Ast2),
83 377 {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 152 [schema_pattern(SchemaDir), schema_pattern("global")].
111
112 -spec schema_pattern(file:name_all()) -> file:filename_all().
113 schema_pattern(DirName) ->
114 304 schema_pattern(DirName, "*.gql").
115
116 -spec schema_pattern(file:name_all(), file:name_all()) -> file:filename_all().
117 schema_pattern(DirName, Pattern) ->
118 304 filename:join([code:priv_dir(mongooseim), "graphql/schemas", DirName, Pattern]).
119
120 graphql_parse(Doc) ->
121 386 case graphql:parse(Doc) of
122 {ok, _} = Ok ->
123 385 Ok;
124 {error, Err} ->
125 1 graphql_err:abort([], parse, Err)
126 end.
127
128 admin_mapping_rules() ->
129 76 #{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 'AccountAdminQuery' => mongoose_graphql_account_admin_query,
140 'AccountAdminMutation' => mongoose_graphql_account_admin_mutation,
141 'MUCAdminMutation' => mongoose_graphql_muc_admin_mutation,
142 'MUCAdminQuery' => mongoose_graphql_muc_admin_query,
143 'MUCLightAdminMutation' => mongoose_graphql_muc_light_admin_mutation,
144 'MUCLightAdminQuery' => mongoose_graphql_muc_light_admin_query,
145 'RosterAdminQuery' => mongoose_graphql_roster_admin_query,
146 'RosterAdminMutation' => mongoose_graphql_roster_admin_mutation,
147 'Domain' => mongoose_graphql_domain,
148 default => mongoose_graphql_default},
149 interfaces => #{default => mongoose_graphql_default},
150 scalars => #{default => mongoose_graphql_scalar},
151 enums => #{default => mongoose_graphql_enum}}.
152
153 user_mapping_rules() ->
154 76 #{objects => #{
155 'UserQuery' => mongoose_graphql_user_query,
156 'UserMutation' => mongoose_graphql_user_mutation,
157 'AccountUserQuery' => mongoose_graphql_account_user_query,
158 'AccountUserMutation' => mongoose_graphql_account_user_mutation,
159 'MUCUserMutation' => mongoose_graphql_muc_user_mutation,
160 'MUCUserQuery' => mongoose_graphql_muc_user_query,
161 'MUCLightUserMutation' => mongoose_graphql_muc_light_user_mutation,
162 'MUCLightUserQuery' => mongoose_graphql_muc_light_user_query,
163 'RosterUserQuery' => mongoose_graphql_roster_user_query,
164 'RosterUserMutation' => mongoose_graphql_roster_user_mutation,
165 'SessionUserQuery' => mongoose_graphql_session_user_query,
166 'StanzaUserMutation' => mongoose_graphql_stanza_user_mutation,
167 'StanzaUserQuery' => mongoose_graphql_stanza_user_query,
168 'UserAuthInfo' => mongoose_graphql_user_auth_info,
169 default => mongoose_graphql_default},
170 interfaces => #{default => mongoose_graphql_default},
171 scalars => #{default => mongoose_graphql_scalar},
172 enums => #{default => mongoose_graphql_enum}}.
173
174 load_multiple_file_schema(Patterns) ->
175 152 Paths = lists:flatmap(fun(P) -> filelib:wildcard(P) end, Patterns),
176 152 try
177 152 SchemaData = [read_schema_file(P) || P <- Paths],
178 152 {ok, lists:flatten(SchemaData)}
179 catch
180 throw:{error, Reason, Path} ->
181
:-(
?LOG_ERROR(#{what => graphql_cannot_load_schema,
182
:-(
reason => Reason, path => Path}),
183
:-(
{error, cannot_load}
184 end.
185
186 read_schema_file(Path) ->
187 2660 case file:read_file(Path) of
188 {ok, Data} ->
189 2660 binary_to_list(Data);
190 {error, Reason} ->
191
:-(
throw({error, Reason, Path})
192 end.
Line Hits Source