./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, #resolver_error{reason = Code, msg = Msg, context = Ext}) ->
19 450 #{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 15 Msg = #{extensions => #{code => err_code(Phase, Term)},
38 message => iolist_to_binary(err_msg(Phase, Term))},
39 15 {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 92 Err2 = maps:merge(#{path => []}, Err),
45 92 [ErrMsg] = graphql:format_errors(#{}, [Err2]),
46 92 {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 4 401;
60 err_http_code(_) ->
61 11 400.
62
63 err_code(_, Term) ->
64 15 simplify(Term).
65
66 9 simplify(A) when is_atom(A) -> A;
67
:-(
simplify(B) when is_binary(B) -> B;
68 6 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 7 decode_err_msg(Result);
74 err_msg(authorize, Result) ->
75 4 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 2 "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
:-(
Format = "Cannot execute query ~s without a global admin permissions",
87
:-(
io_lib:format(Format, [Op]);
88 authorize_err_msg({no_permissions, Op, #{type := Res, invalid_args := InvalidArgs}}) ->
89
:-(
InvalidArgs2 = lists:join(", ", InvalidArgs),
90
:-(
Format = "Cannot execute query ~s without permissions to the given ~s. "
91 ++ "Args with invalid value: ~s",
92
:-(
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
:-(
"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 6 ErrMsg#{path => Path};
115 add_path(_, ErrMsg) ->
116 9 ErrMsg.
Line Hits Source