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, #{jid := Jid, what := unknown_user}) when is_binary(Jid) -> |
19 |
2 |
#{message => <<"Given user does not exist">>, extensions => #{code => unknown_user, jid => Jid}}; |
20 |
|
err(_Ctx, #{domain := Domain, what := unknown_domain}) when is_binary(Domain) -> |
21 |
1 |
#{message => <<"Given domain does not exist">>, extensions => #{code => unknown_domain, domain => Domain}}; |
22 |
|
err(_Ctx, #{what := bad_from_jid}) -> |
23 |
3 |
#{message => <<"Sending from this JID is not allowed">>, extensions => #{code => bad_from_jid}}; |
24 |
|
err(_Ctx, #{domain := Domain, what := domain_not_found}) -> |
25 |
3 |
#{message => <<"Given domain does not exist">>, extensions => #{code => domain_not_found, domain => Domain}}; |
26 |
|
err(_Ctx, #{domain := Domain, what := domain_duplicate}) when is_binary(Domain) -> |
27 |
1 |
#{message => <<"Domain already exists">>, extensions => #{code => domain_duplicate, domain => Domain}}; |
28 |
|
err(_Ctx, #{domain := Domain, what := domain_static}) when is_binary(Domain) -> |
29 |
1 |
#{message => <<"Domain static">>, extensions => #{code => domain_static, domain => Domain}}; |
30 |
|
err(_Ctx, #{host_type := HostType, what := unknown_host_type}) when is_binary(HostType) -> |
31 |
1 |
#{message => <<"Unknown host type">>, extensions => #{code => unknown_host_type, hostType => HostType}}; |
32 |
|
err(_Ctx, #{host_type := HostType, what := wrong_host_type}) when is_binary(HostType) -> |
33 |
1 |
#{message => <<"Wrong host type">>, extensions => #{code => wrong_host_type, hostType => HostType}}; |
34 |
|
err(_Ctx, #{term := Term, what := db_error}) -> |
35 |
:-( |
#{message => <<"Database error">>, extensions => #{code => db_error, term => Term}}; |
36 |
|
err(_Ctx, #{host_type := HostType, what := service_disabled}) when is_binary(HostType) -> |
37 |
:-( |
#{message => <<"Service disabled">>, extensions => #{code => service_disabled, hostType => HostType}}; |
38 |
|
err(_Ctx, #resolver_error{reason = Code, msg = Msg, context = Ext}) -> |
39 |
83 |
#{message => iolist_to_binary(Msg), extensions => Ext#{code => Code}}; |
40 |
|
err(_Ctx, ErrorTerm) -> |
41 |
:-( |
#{message => iolist_to_binary(io_lib:format("~p", [ErrorTerm])), |
42 |
|
extensions => #{code => resolver_error}}. |
43 |
|
|
44 |
|
%% callback invoked when resolver crashes |
45 |
|
-spec crash(map(), term()) -> err_msg(). |
46 |
|
crash(_Ctx, Err = #{type := Type}) -> |
47 |
:-( |
?LOG_ERROR(Err#{what => graphql_crash}), |
48 |
:-( |
#{message => <<"Unexpected ", Type/binary, " resolver crash">>, |
49 |
|
extensions => #{code => resolver_crash}}. |
50 |
|
|
51 |
|
%% @doc Format error that occurred in any phase including HTTP request decoding. |
52 |
|
-spec format_error(term())-> {integer(), err_msg()}. |
53 |
|
format_error(#{phase := Phase, error_term := Term} = Err) when Phase =:= authorize; |
54 |
|
Phase =:= decode; |
55 |
|
Phase =:= parse -> |
56 |
2 |
Msg = #{extensions => #{code => err_code(Phase, Term)}, |
57 |
|
message => iolist_to_binary(err_msg(Phase, Term))}, |
58 |
2 |
{err_http_code(Phase), add_path(Err, Msg)}; |
59 |
|
format_error(#{error_term := _, phase := Phase} = Err) when Phase =:= execute; |
60 |
|
Phase =:= type_check; |
61 |
|
Phase =:= validate; |
62 |
|
Phase =:= uncategorized -> |
63 |
4 |
Err2 = maps:merge(#{path => []}, Err), |
64 |
4 |
[ErrMsg] = graphql:format_errors(#{}, [Err2]), |
65 |
4 |
{400, ErrMsg}; |
66 |
|
format_error(internal_crash) -> |
67 |
:-( |
Msg = #{message => <<"GraphQL Internal Server Error">>, |
68 |
|
extensions => #{code => internal_server_error}}, |
69 |
:-( |
{500, Msg}; |
70 |
|
format_error(Err) -> |
71 |
:-( |
Msg = #{extensions => #{code => uncategorized}, |
72 |
|
message => iolist_to_binary(io_lib:format("~p", [Err]))}, |
73 |
:-( |
{400, Msg}. |
74 |
|
|
75 |
|
%% Internal |
76 |
|
|
77 |
|
err_http_code(authorize) -> |
78 |
:-( |
401; |
79 |
|
err_http_code(_) -> |
80 |
2 |
400. |
81 |
|
|
82 |
|
err_code(_, Term) -> |
83 |
2 |
simplify(Term). |
84 |
|
|
85 |
2 |
simplify(A) when is_atom(A) -> A; |
86 |
:-( |
simplify(B) when is_binary(B) -> B; |
87 |
:-( |
simplify(T) when is_tuple(T) -> element(1, T). |
88 |
|
|
89 |
|
err_msg(parse, Result) -> |
90 |
:-( |
parse_err_msg(Result); |
91 |
|
err_msg(decode, Result) -> |
92 |
2 |
decode_err_msg(Result); |
93 |
|
err_msg(authorize, Result) -> |
94 |
:-( |
authorize_err_msg(Result). |
95 |
|
|
96 |
|
authorize_err_msg({request_error, {header, <<"authorization">>}, _}) -> |
97 |
:-( |
"Malformed authorization header. Please consult the relevant specification"; |
98 |
|
authorize_err_msg(wrong_credentials) -> |
99 |
:-( |
"The provided credentials are wrong"; |
100 |
|
authorize_err_msg({no_permissions, Op}) -> |
101 |
:-( |
io_lib:format("Cannot execute query ~s without permissions", [Op]). |
102 |
|
|
103 |
|
parse_err_msg({parser_error, {Line, graphql_parser, Msg}}) -> |
104 |
:-( |
io_lib:format("Cannot parse line ~B because of ~s", [Line, Msg]); |
105 |
|
parse_err_msg({scanner_error, {Line, graphql_scanner, Msg}}) -> |
106 |
:-( |
Formatted = lists:flatten(graphql_scanner:format_error(Msg)), |
107 |
:-( |
io_lib:format("Cannot scan line ~B because of ~s", [Line, Formatted]). |
108 |
|
|
109 |
|
decode_err_msg(no_query_supplied) -> |
110 |
2 |
"The query was not supplied in the request body"; |
111 |
|
decode_err_msg(invalid_json_body) -> |
112 |
:-( |
"The request JSON body is invalid"; |
113 |
|
decode_err_msg(variables_invalid_json) -> |
114 |
:-( |
"The variables' JSON is invalid". |
115 |
|
|
116 |
|
add_path(#{path := Path}, ErrMsg) -> |
117 |
:-( |
ErrMsg#{path => Path}; |
118 |
|
add_path(_, ErrMsg) -> |
119 |
2 |
ErrMsg. |