Skip to content

Commit

Permalink
Add better error messages formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
Premwoik committed Dec 15, 2021
1 parent d35b68b commit 866f878
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 19 deletions.
4 changes: 2 additions & 2 deletions src/mongoose_graphql.erl
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ graphql_parse(Doc) ->
case graphql:parse(Doc) of
{ok, _} = Ok ->
Ok;
{error, _} = Err ->
throw(Err)
{error, Err} ->
graphql_err:abort([], parse, Err)
end.

admin_mapping_rules() ->
Expand Down
22 changes: 10 additions & 12 deletions src/mongoose_graphql/mongoose_graphql_cowboy_handler.erl
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,15 @@ charsets_provided(Req, State) ->
{[<<"utf-8">>], Req, State}.

is_authorized(Req, State) ->
Auth = cowboy_req:parse_header(<<"authorization">>, Req),
State2 = check_auth(Auth, State),
{true, Req, State2}.
try cowboy_req:parse_header(<<"authorization">>, Req) of
Auth ->
State2 = check_auth(Auth, State),
{true, Req, State2}
catch
exit:Err ->
Msg = #{phase => authorize, error_term => Err},
reply_error(400, Msg, Req, State)
end.

resource_exists(#{method := <<"GET">>} = Req, State) ->
{true, Req, State};
Expand Down Expand Up @@ -179,15 +185,7 @@ operation_name([]) ->
undefined.

reply_error(Code, Msg, Req, State) ->
Errors =
case Msg of
{error, E} ->
graphql_err:format_errors(#{}, [E]);
_ ->
Formatted = iolist_to_binary(io_lib:format("~p", [Msg])),
[#{type => error, message => Formatted}]
end,

Errors = mongoose_graphql_errors:format_error(Msg),
Body = jiffy:encode(#{errors => Errors}),
Req2 = cowboy_req:set_resp_body(Body, Req),
Reply = cowboy_req:reply(Code, Req2),
Expand Down
40 changes: 38 additions & 2 deletions src/mongoose_graphql/mongoose_graphql_errors.erl
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
%% @doc Implements callbacks that format custom errors returned from resolvers or crashes.
-module(mongoose_graphql_errors).

-export([err/2, crash/2]).
-export([format_error/1, format_errors/1, err/2, crash/2]).

-ignore_xref([err/2, crash/2]).
-ignore_xref([format_error/1, format_errors/1, err/2, crash/2]).

% callback invoked when resolver returns error tuple
err(_Ctx, domain_not_found) ->
Expand All @@ -16,3 +16,39 @@ err(_Ctx, ErrorTerm) ->
crash(_Ctx, #{type := Type}) ->
#{message => <<"Unexpected ", Type/binary, " resolver crash">>,
extensions => #{code => resolver_crash}}.

format_errors(Errors) ->
[format_error(E) || E <- Errors].

format_error(#{phase := Phase, error_term := Term}) when Phase =:= authorize;
Phase =:= parse ->
#{extensions => #{code => err_code(Phase, Term)},
message => iolist_to_binary(err_msg(Phase, Term))};
format_error(#{error_term := _} = Err) ->
graphql:format_errors(#{}, [Err]);
format_error(Err) ->
#{extensions => #{code => uncathegorized_error},
message => iolist_to_binary(io_lib:format("~p", Err))}.

%% Internal

err_code(authorize, _Term) ->
authorization_error;
err_code(parse, Term) ->
element(1, Term).

err_msg(parse, Result) ->
parse_err_msg(Result);
err_msg(authorize, Result) ->
authorize_err_msg(Result).

authorize_err_msg({request_error, {header, <<"authorization">>}, _}) ->
"Malformed authorization header.Please consult the relevant specification.";
authorize_err_msg({no_permissions, Op}) ->
io_lib:format("Cannot execute query ~s without permissions", [Op]).

parse_err_msg({parser_error, {Line, graphql_parser, Msg}}) ->
io_lib:format("Cannot parse line ~B because of ~s", [Line, Msg]);
parse_err_msg({scanner_error, {Line, graphql_scanner, Msg}}) ->
Formatted = lists:flatten(graphql_scanner:format_error(Msg)),
io_lib:format("Cannot scan line ~B because of ~s", [Line, Formatted]).
7 changes: 6 additions & 1 deletion src/mongoose_graphql/mongoose_graphql_permissions.erl
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ check_permissions(OpName, false, #document{definitions = Definitions}) ->
true ->
ok;
false ->
throw({error, no_permissions})
graphql_err:abort([], authorize, {no_permissions, op_name(OpName)})
end;
false ->
ok
Expand All @@ -58,6 +58,11 @@ check_permissions(_, true, _) ->

% Internal

op_name(undefined) ->
<<"ROOT">>;
op_name(Name) ->
Name.

is_req_operation(#op{id = 'ROOT'}, undefined) ->
true;
is_req_operation(#op{id = {name, _, Name}}, Name) ->
Expand Down
4 changes: 2 additions & 2 deletions test/mongoose_graphql_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,13 @@ unauth_cannot_execute_protected_query(Config) ->
Ep = ?config(endpoint, Config),
Doc = <<"{ field }">>,
Res = mongoose_graphql:execute(Ep, request(Doc, false)),
?assertEqual({error, no_permissions}, Res).
?assertMatch({error, #{error_term := {no_permissions, <<"ROOT">>}}}, Res).

unauth_cannot_execute_protected_mutation(Config) ->
Ep = ?config(endpoint, Config),
Doc = <<"mutation { field }">>,
Res = mongoose_graphql:execute(Ep, request(Doc, false)),
?assertEqual({error, no_permissions}, Res).
?assertMatch({error, #{error_term := {no_permissions, <<"ROOT">>}}}, Res).

unauth_can_access_introspection(Config) ->
Ep = ?config(endpoint, Config),
Expand Down

0 comments on commit 866f878

Please sign in to comment.