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. |