./ct_report/coverage/mongoose_graphql_errors.COVER.html

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 1 #{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
:-(
#{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
:-(
#{message => <<"Domain already exists">>, extensions => #{code => domain_duplicate, domain => Domain}};
28 err(_Ctx, #{domain := Domain, what := domain_static}) when is_binary(Domain) ->
29
:-(
#{message => <<"Domain static">>, extensions => #{code => domain_static, domain => Domain}};
30 err(_Ctx, #{host_type := HostType, what := unknown_host_type}) when is_binary(HostType) ->
31
:-(
#{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
:-(
#{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 123 #{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 3 Msg = #{extensions => #{code => err_code(Phase, Term)},
57 message => iolist_to_binary(err_msg(Phase, Term))},
58 3 {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 8 Err2 = maps:merge(#{path => []}, Err),
64 8 [ErrMsg] = graphql:format_errors(#{}, [Err2]),
65 8 {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 3 400.
81
82 err_code(_, Term) ->
83 3 simplify(Term).
84
85 3 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 3 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 authorize_err_msg({no_permissions, Op, Res, InvalidArgs}) ->
103
:-(
InvalidArgs2 = lists:join(", ", InvalidArgs),
104
:-(
Format = "Cannot execute query ~s without permissions to the given ~s. "
105 ++ "Args with invalid value: ~s",
106
:-(
io_lib:format(Format, [Op, Res, InvalidArgs2]).
107
108 parse_err_msg({parser_error, {Line, graphql_parser, Msg}}) ->
109
:-(
io_lib:format("Cannot parse line ~B because of ~s", [Line, Msg]);
110 parse_err_msg({scanner_error, {Line, graphql_scanner, Msg}}) ->
111
:-(
Formatted = lists:flatten(graphql_scanner:format_error(Msg)),
112
:-(
io_lib:format("Cannot scan line ~B because of ~s", [Line, Formatted]).
113
114 decode_err_msg(no_query_supplied) ->
115 3 "The query was not supplied in the request body";
116 decode_err_msg(invalid_json_body) ->
117
:-(
"The request JSON body is invalid";
118 decode_err_msg(variables_invalid_json) ->
119
:-(
"The variables' JSON is invalid".
120
121 add_path(#{path := Path}, ErrMsg) ->
122
:-(
ErrMsg#{path => Path};
123 add_path(_, ErrMsg) ->
124 3 ErrMsg.
Line Hits Source