1 |
|
%% @doc Implements callbacks that format custom errors returned from resolvers or crashes. |
2 |
|
%% In addition, it can format each type of error that occurred in any graphql |
3 |
|
%% or mongoose_graphql phase. |
4 |
|
%% @end |
5 |
|
-module(mongoose_graphql_errors). |
6 |
|
|
7 |
|
-export([format_error/1, err/2, crash/2]). |
8 |
|
|
9 |
|
-ignore_xref([format_error/1, err/2, crash/2]). |
10 |
|
|
11 |
|
-include("mongoose_graphql_types.hrl"). |
12 |
|
-include("mongoose_logger.hrl"). |
13 |
|
|
14 |
|
-type err_msg() :: #{message := binary(), extensions => map(), path => list()}. |
15 |
|
|
16 |
|
%% callback invoked when resolver returns error tuple |
17 |
|
-spec err(map(), term()) -> err_msg(). |
18 |
|
err(_Ctx, #resolver_error{reason = Code, msg = Msg, context = Ext}) -> |
19 |
588 |
#{message => iolist_to_binary(Msg), extensions => Ext#{code => Code}}; |
20 |
|
err(_Ctx, ErrorTerm) -> |
21 |
:-( |
#{message => iolist_to_binary(io_lib:format("~p", [ErrorTerm])), |
22 |
|
extensions => #{code => resolver_error}}. |
23 |
|
|
24 |
|
%% callback invoked when resolver crashes |
25 |
|
-spec crash(map(), term()) -> err_msg(). |
26 |
|
crash(_Ctx, Err = #{type := Type}) -> |
27 |
:-( |
?LOG_ERROR(Err#{what => graphql_crash}), |
28 |
:-( |
#{message => <<"Unexpected ", Type/binary, " resolver crash">>, |
29 |
|
extensions => #{code => resolver_crash}}. |
30 |
|
|
31 |
|
%% @doc Format error that occurred in any phase including HTTP request decoding. |
32 |
|
-spec format_error(term())-> {integer(), err_msg()}. |
33 |
|
format_error(#{phase := Phase, error_term := Term} = Err) when Phase =:= authorize; |
34 |
|
Phase =:= decode; |
35 |
|
Phase =:= parse; |
36 |
|
Phase =:= verify -> |
37 |
210 |
Msg = #{extensions => #{code => err_code(Phase, Term)}, |
38 |
|
message => iolist_to_binary(err_msg(Phase, Term))}, |
39 |
210 |
{err_http_code(Phase), add_path(Err, Msg)}; |
40 |
|
format_error(#{error_term := _, phase := Phase} = Err) when Phase =:= execute; |
41 |
|
Phase =:= type_check; |
42 |
|
Phase =:= validate; |
43 |
|
Phase =:= uncategorized -> |
44 |
113 |
Err2 = maps:merge(#{path => []}, Err), |
45 |
113 |
[ErrMsg] = graphql:format_errors(#{}, [Err2]), |
46 |
113 |
{400, ErrMsg}; |
47 |
|
format_error(internal_crash) -> |
48 |
:-( |
Msg = #{message => <<"GraphQL Internal Server Error">>, |
49 |
|
extensions => #{code => internal_server_error}}, |
50 |
:-( |
{500, Msg}; |
51 |
|
format_error(Err) -> |
52 |
:-( |
Msg = #{extensions => #{code => uncategorized}, |
53 |
|
message => iolist_to_binary(io_lib:format("~p", [Err]))}, |
54 |
:-( |
{400, Msg}. |
55 |
|
|
56 |
|
%% Internal |
57 |
|
|
58 |
|
err_http_code(authorize) -> |
59 |
195 |
401; |
60 |
|
err_http_code(_) -> |
61 |
15 |
400. |
62 |
|
|
63 |
|
err_code(_, Term) -> |
64 |
210 |
simplify(Term). |
65 |
|
|
66 |
29 |
simplify(A) when is_atom(A) -> A; |
67 |
:-( |
simplify(B) when is_binary(B) -> B; |
68 |
181 |
simplify(T) when is_tuple(T) -> element(1, T). |
69 |
|
|
70 |
|
err_msg(parse, Result) -> |
71 |
2 |
parse_err_msg(Result); |
72 |
|
err_msg(decode, Result) -> |
73 |
11 |
decode_err_msg(Result); |
74 |
|
err_msg(authorize, Result) -> |
75 |
195 |
authorize_err_msg(Result); |
76 |
|
err_msg(verify, Result) -> |
77 |
2 |
verify_err_msg(Result). |
78 |
|
|
79 |
|
authorize_err_msg({request_error, {header, <<"authorization">>}, _}) -> |
80 |
:-( |
"Malformed authorization header. Please consult the relevant specification"; |
81 |
|
authorize_err_msg(wrong_credentials) -> |
82 |
18 |
"The provided credentials are wrong"; |
83 |
|
authorize_err_msg({no_permissions, Op}) -> |
84 |
2 |
io_lib:format("Cannot execute query ~s without permissions", [Op]); |
85 |
|
authorize_err_msg({no_permissions, Op, #{type := global}}) -> |
86 |
19 |
Format = "Cannot execute query ~s without a global admin permissions", |
87 |
19 |
io_lib:format(Format, [Op]); |
88 |
|
authorize_err_msg({no_permissions, Op, #{type := Res, invalid_args := InvalidArgs}}) -> |
89 |
156 |
InvalidArgs2 = lists:join(", ", InvalidArgs), |
90 |
156 |
Format = "Cannot execute query ~s without permissions to the given ~s. " |
91 |
|
++ "Args with invalid value: ~s", |
92 |
156 |
io_lib:format(Format, [Op, Res, InvalidArgs2]). |
93 |
|
|
94 |
|
parse_err_msg({parser_error, {Line, graphql_parser, Msg}}) -> |
95 |
2 |
io_lib:format("Cannot parse line ~B because of ~s", [Line, Msg]); |
96 |
|
parse_err_msg({scanner_error, {Line, graphql_scanner, Msg}}) -> |
97 |
:-( |
Formatted = lists:flatten(graphql_scanner:format_error(Msg)), |
98 |
:-( |
io_lib:format("Cannot scan line ~B because of ~s", [Line, Formatted]). |
99 |
|
|
100 |
|
decode_err_msg(no_query_supplied) -> |
101 |
5 |
"The query was not supplied in the request body"; |
102 |
|
decode_err_msg(invalid_json_body) -> |
103 |
4 |
"The request JSON body is invalid"; |
104 |
|
decode_err_msg(invalid_query_parameters) -> |
105 |
2 |
"The query string is invalid"; |
106 |
|
decode_err_msg(variables_invalid_json) -> |
107 |
:-( |
"The variables' JSON is invalid". |
108 |
|
|
109 |
|
verify_err_msg({unsupported_operation, Method, Operation}) -> |
110 |
2 |
io_lib:format("The ~p execution method does not support ~p operations.", |
111 |
|
[Method, Operation]). |
112 |
|
|
113 |
|
add_path(#{path := Path}, ErrMsg) -> |
114 |
181 |
ErrMsg#{path => Path}; |
115 |
|
add_path(_, ErrMsg) -> |
116 |
29 |
ErrMsg. |