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 |
|
|
23 |
|
-export_type([request/0]). |
24 |
|
|
25 |
|
-define(USER_EP_NAME, user_schema_ep). |
26 |
|
-define(ADMIN_EP_NAME, admin_schema_ep). |
27 |
|
|
28 |
|
%% @doc Create and initialize endpoints for user and admin. |
29 |
|
-spec init() -> ok. |
30 |
|
init() -> |
31 |
80 |
create_endpoint(?USER_EP_NAME, user_mapping_rules(), schema_global_patterns("user")), |
32 |
80 |
create_endpoint(?ADMIN_EP_NAME, admin_mapping_rules(), schema_global_patterns("admin")), |
33 |
80 |
ok. |
34 |
|
|
35 |
|
%% @doc Get endpoint_context for passed endpoint name. |
36 |
|
-spec get_endpoint(atom()) -> graphql:endpoint_context(). |
37 |
|
get_endpoint(admin) -> |
38 |
88 |
graphql_schema:get_endpoint_ctx(?ADMIN_EP_NAME); |
39 |
|
get_endpoint(user) -> |
40 |
50 |
graphql_schema:get_endpoint_ctx(?USER_EP_NAME); |
41 |
|
get_endpoint(Name) -> |
42 |
:-( |
graphql_schema:get_endpoint_ctx(Name). |
43 |
|
|
44 |
|
%% @doc Create a new endpoint and load schema. |
45 |
|
-spec create_endpoint(atom(), map(), [file:filename_all()]) -> gen:start_ret(). |
46 |
|
create_endpoint(Name, Mapping, Patterns) -> |
47 |
160 |
Res = graphql_schema:start_link(Name), |
48 |
160 |
Ep = graphql_schema:get_endpoint_ctx(Name), |
49 |
160 |
{ok, SchemaData} = load_multiple_file_schema(Patterns), |
50 |
160 |
ok = graphql:load_schema(Ep, Mapping, SchemaData), |
51 |
160 |
ok = graphql:validate_schema(Ep), |
52 |
160 |
Res. |
53 |
|
|
54 |
|
%% @doc Execute request on a given endpoint. |
55 |
|
-spec execute(graphql:endpoint_context(), request()) -> |
56 |
|
{ok, map()} | {error, term()}. |
57 |
|
execute(Ep, #{document := Doc, |
58 |
|
operation_name := OpName, |
59 |
|
authorized := AuthStatus, |
60 |
|
vars := Vars, |
61 |
|
ctx := Ctx}) -> |
62 |
138 |
try |
63 |
138 |
{ok, Ast} = graphql_parse(Doc), |
64 |
137 |
{ok, #{ast := Ast2, |
65 |
|
fun_env := FunEnv}} = graphql:type_check(Ep, Ast), |
66 |
137 |
ok = graphql:validate(Ast2), |
67 |
137 |
ok = mongoose_graphql_permissions:check_permissions(OpName, AuthStatus, Ast2), |
68 |
137 |
Coerced = graphql:type_check_params(Ep, FunEnv, OpName, Vars), |
69 |
135 |
Ctx2 = Ctx#{params => Coerced, |
70 |
|
operation_name => OpName, |
71 |
|
authorized => AuthStatus, |
72 |
|
error_module => mongoose_graphql_errors}, |
73 |
135 |
{ok, graphql:execute(Ep, Ctx2, Ast2)} |
74 |
|
catch |
75 |
|
throw:{error, Err} -> |
76 |
3 |
{error, Err}; |
77 |
|
Class:Reason:Stacktrace -> |
78 |
:-( |
Err = #{what => graphql_internal_crash, |
79 |
|
class => Class, reason => Reason, |
80 |
|
stacktrace => Stacktrace}, |
81 |
:-( |
?LOG_ERROR(Err), |
82 |
:-( |
{error, internal_crash} |
83 |
|
end. |
84 |
|
|
85 |
|
%% @doc Execute selected operation on a given endpoint with authorization. |
86 |
|
-spec execute(graphql:endpoint_context(), undefined | binary(), binary()) -> |
87 |
|
{ok, map()} | {error, term()}. |
88 |
|
execute(Ep, OpName, Doc) -> |
89 |
2 |
Req = #{document => Doc, |
90 |
|
operation_name => OpName, |
91 |
|
vars => #{}, |
92 |
|
authorized => true, |
93 |
|
ctx => #{}}, |
94 |
2 |
execute(Ep, Req). |
95 |
|
|
96 |
|
% Internal |
97 |
|
|
98 |
|
-spec schema_global_patterns(file:name_all()) -> [file:filename_all()]. |
99 |
|
schema_global_patterns(SchemaDir) -> |
100 |
160 |
[schema_pattern(SchemaDir), schema_pattern("global")]. |
101 |
|
|
102 |
|
-spec schema_pattern(file:name_all()) -> file:filename_all(). |
103 |
|
schema_pattern(DirName) -> |
104 |
320 |
schema_pattern(DirName, "*.gql"). |
105 |
|
|
106 |
|
-spec schema_pattern(file:name_all(), file:name_all()) -> file:filename_all(). |
107 |
|
schema_pattern(DirName, Pattern) -> |
108 |
320 |
filename:join([code:priv_dir(mongooseim), "graphql/schemas", DirName, Pattern]). |
109 |
|
|
110 |
|
graphql_parse(Doc) -> |
111 |
138 |
case graphql:parse(Doc) of |
112 |
|
{ok, _} = Ok -> |
113 |
137 |
Ok; |
114 |
|
{error, Err} -> |
115 |
1 |
graphql_err:abort([], parse, Err) |
116 |
|
end. |
117 |
|
|
118 |
|
admin_mapping_rules() -> |
119 |
80 |
#{objects => #{ |
120 |
|
'AdminQuery' => mongoose_graphql_admin_query, |
121 |
|
'DomainAdminQuery' => mongoose_graphql_domain_admin_query, |
122 |
|
'AdminMutation' => mongoose_graphql_admin_mutation, |
123 |
|
'DomainAdminMutation' => mongoose_graphql_domain_admin_mutation, |
124 |
|
'SessionAdminMutation' => mongoose_graphql_session_admin_mutation, |
125 |
|
'SessionAdminQuery' => mongoose_graphql_session_admin_query, |
126 |
|
'StanzaAdminMutation' => mongoose_graphql_stanza_admin_mutation, |
127 |
|
'StanzaAdminQuery' => mongoose_graphql_stanza_admin_query, |
128 |
|
'AccountAdminQuery' => mongoose_graphql_account_admin_query, |
129 |
|
'AccountAdminMutation' => mongoose_graphql_account_admin_mutation, |
130 |
|
'MUCLightAdminMutation' => mongoose_graphql_muc_light_admin_mutation, |
131 |
|
'MUCLightAdminQuery' => mongoose_graphql_muc_light_admin_query, |
132 |
|
'Domain' => mongoose_graphql_domain, |
133 |
|
default => mongoose_graphql_default}, |
134 |
|
interfaces => #{default => mongoose_graphql_default}, |
135 |
|
scalars => #{default => mongoose_graphql_scalar}, |
136 |
|
enums => #{default => mongoose_graphql_enum}}. |
137 |
|
|
138 |
|
user_mapping_rules() -> |
139 |
80 |
#{objects => #{ |
140 |
|
'UserQuery' => mongoose_graphql_user_query, |
141 |
|
'UserMutation' => mongoose_graphql_user_mutation, |
142 |
|
'AccountUserQuery' => mongoose_graphql_account_user_query, |
143 |
|
'AccountUserMutation' => mongoose_graphql_account_user_mutation, |
144 |
|
'MUCLightUserMutation' => mongoose_graphql_muc_light_user_mutation, |
145 |
|
'MUCLightUserQuery' => mongoose_graphql_muc_light_user_query, |
146 |
|
'SessionUserQuery' => mongoose_graphql_session_user_query, |
147 |
|
'UserAuthInfo' => mongoose_graphql_user_auth_info, |
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 |
|
load_multiple_file_schema(Patterns) -> |
154 |
160 |
Paths = lists:flatmap(fun(P) -> filelib:wildcard(P) end, Patterns), |
155 |
160 |
try |
156 |
160 |
SchemaData = [read_schema_file(P) || P <- Paths], |
157 |
160 |
{ok, lists:flatten(SchemaData)} |
158 |
|
catch |
159 |
|
throw:{error, Reason, Path} -> |
160 |
:-( |
?LOG_ERROR(#{what => graphql_cannot_load_schema, |
161 |
:-( |
reason => Reason, path => Path}), |
162 |
:-( |
{error, cannot_load} |
163 |
|
end. |
164 |
|
|
165 |
|
read_schema_file(Path) -> |
166 |
2000 |
case file:read_file(Path) of |
167 |
|
{ok, Data} -> |
168 |
2000 |
binary_to_list(Data); |
169 |
|
{error, Reason} -> |
170 |
:-( |
throw({error, Reason, Path}) |
171 |
|
end. |