Merge pull request #10050 from sstrigler/EMQX-9063-bridges-api-consistently-return-404-if-resource-does-not-exist

fix: consistently return 404 in case bridge is not found or invalid
This commit is contained in:
Stefan Strigler 2023-03-02 11:20:56 +01:00 committed by GitHub
commit 9fb74bfc87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 41 additions and 30 deletions

View File

@ -46,17 +46,22 @@
-export([lookup_from_local_node/2]). -export([lookup_from_local_node/2]).
-define(NOT_FOUND(Reason), {404, error_msg('NOT_FOUND', Reason)}).
-define(BRIDGE_NOT_FOUND(Type, Name),
?NOT_FOUND(
<<"Bridge lookup failed: bridge named '", Name/binary, "' of type ",
(atom_to_binary(Type))/binary, " does not exist.">>
)
).
-define(TRY_PARSE_ID(ID, EXPR), -define(TRY_PARSE_ID(ID, EXPR),
try emqx_bridge_resource:parse_bridge_id(Id) of try emqx_bridge_resource:parse_bridge_id(Id) of
{BridgeType, BridgeName} -> {BridgeType, BridgeName} ->
EXPR EXPR
catch catch
throw:{invalid_bridge_id, Reason} -> throw:{invalid_bridge_id, Reason} ->
{400, ?NOT_FOUND(<<"Invalid bridge ID, ", Reason/binary>>)
error_msg(
'INVALID_ID',
<<"Invalid bride ID, ", Reason/binary>>
)}
end end
). ).
@ -338,7 +343,7 @@ schema("/bridges/:id") ->
responses => #{ responses => #{
200 => get_response_body_schema(), 200 => get_response_body_schema(),
404 => error_schema('NOT_FOUND', "Bridge not found"), 404 => error_schema('NOT_FOUND', "Bridge not found"),
400 => error_schema(['BAD_REQUEST', 'INVALID_ID'], "Update bridge failed") 400 => error_schema('BAD_REQUEST', "Update bridge failed")
} }
}, },
delete => #{ delete => #{
@ -348,7 +353,6 @@ schema("/bridges/:id") ->
parameters => [param_path_id()], parameters => [param_path_id()],
responses => #{ responses => #{
204 => <<"Bridge deleted">>, 204 => <<"Bridge deleted">>,
400 => error_schema(['INVALID_ID'], "Update bridge failed"),
404 => error_schema('NOT_FOUND', "Bridge not found"), 404 => error_schema('NOT_FOUND', "Bridge not found"),
403 => error_schema('FORBIDDEN_REQUEST', "Forbidden operation"), 403 => error_schema('FORBIDDEN_REQUEST', "Forbidden operation"),
503 => error_schema('SERVICE_UNAVAILABLE', "Service unavailable") 503 => error_schema('SERVICE_UNAVAILABLE', "Service unavailable")
@ -379,7 +383,8 @@ schema("/bridges/:id/metrics/reset") ->
parameters => [param_path_id()], parameters => [param_path_id()],
responses => #{ responses => #{
204 => <<"Reset success">>, 204 => <<"Reset success">>,
400 => error_schema(['BAD_REQUEST'], "RPC Call Failed") 400 => error_schema(['BAD_REQUEST'], "RPC Call Failed"),
404 => error_schema('NOT_FOUND', "Bridge not found")
} }
} }
}; };
@ -395,7 +400,7 @@ schema("/bridges/:id/enable/:enable") ->
responses => responses =>
#{ #{
204 => <<"Success">>, 204 => <<"Success">>,
400 => error_schema('INVALID_ID', "Bad bridge ID"), 404 => error_schema('NOT_FOUND', "Bridge not found or invalid operation"),
503 => error_schema('SERVICE_UNAVAILABLE', "Service unavailable") 503 => error_schema('SERVICE_UNAVAILABLE', "Service unavailable")
} }
} }
@ -413,7 +418,7 @@ schema("/bridges/:id/:operation") ->
], ],
responses => #{ responses => #{
204 => <<"Operation success">>, 204 => <<"Operation success">>,
400 => error_schema('INVALID_ID', "Bad bridge ID"), 404 => error_schema('NOT_FOUND', "Bridge not found or invalid operation"),
501 => error_schema('NOT_IMPLEMENTED', "Not Implemented"), 501 => error_schema('NOT_IMPLEMENTED', "Not Implemented"),
503 => error_schema('SERVICE_UNAVAILABLE', "Service unavailable") 503 => error_schema('SERVICE_UNAVAILABLE', "Service unavailable")
} }
@ -433,7 +438,7 @@ schema("/nodes/:node/bridges/:id/:operation") ->
], ],
responses => #{ responses => #{
204 => <<"Operation success">>, 204 => <<"Operation success">>,
400 => error_schema('INVALID_ID', "Bad bridge ID"), 404 => error_schema('NOT_FOUND', "Bridge not found or invalid operation"),
403 => error_schema('FORBIDDEN_REQUEST', "forbidden operation"), 403 => error_schema('FORBIDDEN_REQUEST', "forbidden operation"),
501 => error_schema('NOT_IMPLEMENTED', "Not Implemented"), 501 => error_schema('NOT_IMPLEMENTED', "Not Implemented"),
503 => error_schema('SERVICE_UNAVAILABLE', "Service unavailable") 503 => error_schema('SERVICE_UNAVAILABLE', "Service unavailable")
@ -493,24 +498,26 @@ schema("/bridges_probe") ->
{400, Error} {400, Error}
end; end;
{error, not_found} -> {error, not_found} ->
{404, error_msg('NOT_FOUND', <<"bridge not found">>)} ?BRIDGE_NOT_FOUND(BridgeType, BridgeName)
end end
); );
'/bridges/:id'(delete, #{bindings := #{id := Id}, query_string := Qs}) -> '/bridges/:id'(delete, #{bindings := #{id := Id}, query_string := Qs}) ->
AlsoDeleteActs =
case maps:get(<<"also_delete_dep_actions">>, Qs, <<"false">>) of
<<"true">> -> true;
true -> true;
_ -> false
end,
?TRY_PARSE_ID( ?TRY_PARSE_ID(
Id, Id,
case emqx_bridge:lookup(BridgeType, BridgeName) of case emqx_bridge:lookup(BridgeType, BridgeName) of
{ok, _} -> {ok, _} ->
AlsoDeleteActs =
case maps:get(<<"also_delete_dep_actions">>, Qs, <<"false">>) of
<<"true">> -> true;
true -> true;
_ -> false
end,
case emqx_bridge:check_deps_and_remove(BridgeType, BridgeName, AlsoDeleteActs) of case emqx_bridge:check_deps_and_remove(BridgeType, BridgeName, AlsoDeleteActs) of
{ok, _} -> {ok, _} ->
204; 204;
{error, {rules_deps_on_this_bridge, RuleIds}} -> {error, {rules_deps_on_this_bridge, RuleIds}} ->
%% [FIXME] this should be a 400 since '403' is about
%% authorization and not application logic.
{403, {403,
error_msg( error_msg(
'FORBIDDEN_REQUEST', 'FORBIDDEN_REQUEST',
@ -522,7 +529,7 @@ schema("/bridges_probe") ->
{500, error_msg('INTERNAL_ERROR', Reason)} {500, error_msg('INTERNAL_ERROR', Reason)}
end; end;
{error, not_found} -> {error, not_found} ->
{404, error_msg('NOT_FOUND', <<"Bridge not found">>)} ?BRIDGE_NOT_FOUND(BridgeType, BridgeName)
end end
). ).
@ -570,7 +577,7 @@ do_lookup_from_all_nodes(BridgeType, BridgeName, SuccCode, FormatFun) ->
{ok, [{ok, _} | _] = Results} -> {ok, [{ok, _} | _] = Results} ->
{SuccCode, FormatFun([R || {ok, R} <- Results])}; {SuccCode, FormatFun([R || {ok, R} <- Results])};
{ok, [{error, not_found} | _]} -> {ok, [{error, not_found} | _]} ->
{404, error_msg('NOT_FOUND', <<"not_found">>)}; ?BRIDGE_NOT_FOUND(BridgeType, BridgeName);
{error, ErrL} -> {error, ErrL} ->
{500, error_msg('INTERNAL_ERROR', ErrL)} {500, error_msg('INTERNAL_ERROR', ErrL)}
end. end.
@ -586,13 +593,13 @@ lookup_from_local_node(BridgeType, BridgeName) ->
Id, Id,
case enable_func(Enable) of case enable_func(Enable) of
invalid -> invalid ->
{400, error_msg('BAD_REQUEST', <<"invalid operation">>)}; ?NOT_FOUND(<<"Invalid operation">>);
OperFunc -> OperFunc ->
case emqx_bridge:disable_enable(OperFunc, BridgeType, BridgeName) of case emqx_bridge:disable_enable(OperFunc, BridgeType, BridgeName) of
{ok, _} -> {ok, _} ->
{204}; {204};
{error, {pre_config_update, _, bridge_not_found}} -> {error, {pre_config_update, _, bridge_not_found}} ->
{404, error_msg('NOT_FOUND', <<"bridge not found">>)}; ?BRIDGE_NOT_FOUND(BridgeType, BridgeName);
{error, {_, _, timeout}} -> {error, {_, _, timeout}} ->
{503, error_msg('SERVICE_UNAVAILABLE', <<"request timeout">>)}; {503, error_msg('SERVICE_UNAVAILABLE', <<"request timeout">>)};
{error, timeout} -> {error, timeout} ->
@ -611,7 +618,7 @@ lookup_from_local_node(BridgeType, BridgeName) ->
Id, Id,
case operation_to_all_func(Op) of case operation_to_all_func(Op) of
invalid -> invalid ->
{400, error_msg('BAD_REQUEST', <<"invalid operation">>)}; ?NOT_FOUND(<<"Invalid operation: ", Op/binary>>);
OperFunc -> OperFunc ->
Nodes = mria_mnesia:running_nodes(), Nodes = mria_mnesia:running_nodes(),
call_operation(all, OperFunc, [Nodes, BridgeType, BridgeName]) call_operation(all, OperFunc, [Nodes, BridgeType, BridgeName])
@ -626,11 +633,13 @@ lookup_from_local_node(BridgeType, BridgeName) ->
Id, Id,
case node_operation_func(Op) of case node_operation_func(Op) of
invalid -> invalid ->
{400, error_msg('BAD_REQUEST', <<"invalid operation">>)}; ?NOT_FOUND(<<"Invalid operation: ", Op/binary>>);
OperFunc -> OperFunc ->
ConfMap = emqx:get_config([bridges, BridgeType, BridgeName]), ConfMap = emqx:get_config([bridges, BridgeType, BridgeName]),
case maps:get(enable, ConfMap, false) of case maps:get(enable, ConfMap, false) of
false -> false ->
%% [FIXME] `403` is about authorization not application
%% logic.
{403, {403,
error_msg( error_msg(
'FORBIDDEN_REQUEST', 'FORBIDDEN_REQUEST',
@ -643,7 +652,7 @@ lookup_from_local_node(BridgeType, BridgeName) ->
TargetNode, BridgeType, BridgeName TargetNode, BridgeType, BridgeName
]); ]);
{error, _} -> {error, _} ->
{400, error_msg('INVALID_NODE', <<"invalid node">>)} ?NOT_FOUND(<<"Invalid node name: ", Node/binary>>)
end end
end end
end end

View File

@ -79,7 +79,7 @@ parse_bridge_id(BridgeId) ->
{to_type_atom(Type), validate_name(Name)}; {to_type_atom(Type), validate_name(Name)};
_ -> _ ->
invalid_bridge_id( invalid_bridge_id(
<<"should be of forst {type}:{name}, but got ", BridgeId/binary>> <<"should be of pattern {type}:{name}, but got ", BridgeId/binary>>
) )
end. end.

View File

@ -311,8 +311,8 @@ t_http_crud_apis(Config) ->
), ),
?assertMatch( ?assertMatch(
#{ #{
<<"code">> := _, <<"code">> := <<"NOT_FOUND">>,
<<"message">> := <<"bridge not found">> <<"message">> := _
}, },
jsx:decode(ErrMsg2) jsx:decode(ErrMsg2)
), ),
@ -320,8 +320,8 @@ t_http_crud_apis(Config) ->
{ok, 404, ErrMsg3} = request(delete, uri(["bridges", BridgeID]), []), {ok, 404, ErrMsg3} = request(delete, uri(["bridges", BridgeID]), []),
?assertMatch( ?assertMatch(
#{ #{
<<"code">> := _, <<"code">> := <<"NOT_FOUND">>,
<<"message">> := <<"Bridge not found">> <<"message">> := _
}, },
jsx:decode(ErrMsg3) jsx:decode(ErrMsg3)
), ),

View File

@ -0,0 +1 @@
Ensure Bridge API returns `404` status code consistently for resources that don't exist.

View File

@ -0,0 +1 @@
确保 Bridge API 对不存在的资源一致返回 `404` 状态代码。