Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: consistently return 404 in case bridge is not found or invalid #10050

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
59 changes: 34 additions & 25 deletions apps/emqx_bridge/src/emqx_bridge_api.erl
Expand Up @@ -46,17 +46,22 @@

-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),
try emqx_bridge_resource:parse_bridge_id(Id) of
{BridgeType, BridgeName} ->
EXPR
catch
throw:{invalid_bridge_id, Reason} ->
{400,
error_msg(
'INVALID_ID',
<<"Invalid bride ID, ", Reason/binary>>
)}
?NOT_FOUND(<<"Invalid bridge ID, ", Reason/binary>>)
end
).

Expand Down Expand Up @@ -338,7 +343,7 @@ schema("/bridges/:id") ->
responses => #{
200 => get_response_body_schema(),
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 => #{
Expand All @@ -348,7 +353,6 @@ schema("/bridges/:id") ->
parameters => [param_path_id()],
responses => #{
204 => <<"Bridge deleted">>,
400 => error_schema(['INVALID_ID'], "Update bridge failed"),
404 => error_schema('NOT_FOUND', "Bridge not found"),
403 => error_schema('FORBIDDEN_REQUEST', "Forbidden operation"),
503 => error_schema('SERVICE_UNAVAILABLE', "Service unavailable")
Expand Down Expand Up @@ -379,7 +383,8 @@ schema("/bridges/:id/metrics/reset") ->
parameters => [param_path_id()],
responses => #{
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")
}
}
};
Expand All @@ -395,7 +400,7 @@ schema("/bridges/:id/enable/:enable") ->
responses =>
#{
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")
}
}
Expand All @@ -413,7 +418,7 @@ schema("/bridges/:id/:operation") ->
],
responses => #{
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"),
503 => error_schema('SERVICE_UNAVAILABLE', "Service unavailable")
}
Expand All @@ -433,7 +438,7 @@ schema("/nodes/:node/bridges/:id/:operation") ->
],
responses => #{
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"),
501 => error_schema('NOT_IMPLEMENTED', "Not Implemented"),
503 => error_schema('SERVICE_UNAVAILABLE', "Service unavailable")
Expand Down Expand Up @@ -493,24 +498,26 @@ schema("/bridges_probe") ->
{400, Error}
end;
{error, not_found} ->
{404, error_msg('NOT_FOUND', <<"bridge not found">>)}
?BRIDGE_NOT_FOUND(BridgeType, BridgeName)
end
);
'/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(
Id,
case emqx_bridge:lookup(BridgeType, BridgeName) of
{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
{ok, _} ->
204;
{error, {rules_deps_on_this_bridge, RuleIds}} ->
%% [FIXME] this should be a 400 since '403' is about
%% authorization and not application logic.
id marked this conversation as resolved.
Show resolved Hide resolved
{403,
error_msg(
'FORBIDDEN_REQUEST',
Expand All @@ -522,7 +529,7 @@ schema("/bridges_probe") ->
{500, error_msg('INTERNAL_ERROR', Reason)}
end;
{error, not_found} ->
{404, error_msg('NOT_FOUND', <<"Bridge not found">>)}
?BRIDGE_NOT_FOUND(BridgeType, BridgeName)
end
).

Expand Down Expand Up @@ -570,7 +577,7 @@ do_lookup_from_all_nodes(BridgeType, BridgeName, SuccCode, FormatFun) ->
{ok, [{ok, _} | _] = Results} ->
{SuccCode, FormatFun([R || {ok, R} <- Results])};
{ok, [{error, not_found} | _]} ->
{404, error_msg('NOT_FOUND', <<"not_found">>)};
?BRIDGE_NOT_FOUND(BridgeType, BridgeName);
{error, ErrL} ->
{500, error_msg('INTERNAL_ERROR', ErrL)}
end.
Expand All @@ -586,13 +593,13 @@ lookup_from_local_node(BridgeType, BridgeName) ->
Id,
case enable_func(Enable) of
invalid ->
{400, error_msg('BAD_REQUEST', <<"invalid operation">>)};
?NOT_FOUND(<<"Invalid operation">>);
OperFunc ->
case emqx_bridge:disable_enable(OperFunc, BridgeType, BridgeName) of
{ok, _} ->
{204};
{error, {pre_config_update, _, bridge_not_found}} ->
{404, error_msg('NOT_FOUND', <<"bridge not found">>)};
?BRIDGE_NOT_FOUND(BridgeType, BridgeName);
{error, {_, _, timeout}} ->
{503, error_msg('SERVICE_UNAVAILABLE', <<"request timeout">>)};
{error, timeout} ->
Expand All @@ -611,7 +618,7 @@ lookup_from_local_node(BridgeType, BridgeName) ->
Id,
case operation_to_all_func(Op) of
invalid ->
{400, error_msg('BAD_REQUEST', <<"invalid operation">>)};
?NOT_FOUND(<<"Invalid operation: ", Op/binary>>);
OperFunc ->
Nodes = mria_mnesia:running_nodes(),
call_operation(all, OperFunc, [Nodes, BridgeType, BridgeName])
Expand All @@ -626,11 +633,13 @@ lookup_from_local_node(BridgeType, BridgeName) ->
Id,
case node_operation_func(Op) of
invalid ->
{400, error_msg('BAD_REQUEST', <<"invalid operation">>)};
?NOT_FOUND(<<"Invalid operation: ", Op/binary>>);
OperFunc ->
ConfMap = emqx:get_config([bridges, BridgeType, BridgeName]),
case maps:get(enable, ConfMap, false) of
false ->
%% [FIXME] `403` is about authorization not application
%% logic.
{403,
error_msg(
'FORBIDDEN_REQUEST',
Expand All @@ -643,7 +652,7 @@ lookup_from_local_node(BridgeType, BridgeName) ->
TargetNode, BridgeType, BridgeName
]);
{error, _} ->
{400, error_msg('INVALID_NODE', <<"invalid node">>)}
?NOT_FOUND(<<"Invalid node name: ", Node/binary>>)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion apps/emqx_bridge/src/emqx_bridge_resource.erl
Expand Up @@ -79,7 +79,7 @@ parse_bridge_id(BridgeId) ->
{to_type_atom(Type), validate_name(Name)};
_ ->
invalid_bridge_id(
<<"should be of forst {type}:{name}, but got ", BridgeId/binary>>
<<"should be of pattern {type}:{name}, but got ", BridgeId/binary>>
)
end.

Expand Down
8 changes: 4 additions & 4 deletions apps/emqx_bridge/test/emqx_bridge_api_SUITE.erl
Expand Up @@ -311,17 +311,17 @@ t_http_crud_apis(Config) ->
),
?assertMatch(
#{
<<"code">> := _,
<<"message">> := <<"bridge not found">>
<<"code">> := <<"NOT_FOUND">>,
<<"message">> := _
},
jsx:decode(ErrMsg2)
),
%% Deleting a non-existing bridge should result in an error
{ok, 404, ErrMsg3} = request(delete, uri(["bridges", BridgeID]), []),
?assertMatch(
#{
<<"code">> := _,
<<"message">> := <<"Bridge not found">>
<<"code">> := <<"NOT_FOUND">>,
<<"message">> := _
},
jsx:decode(ErrMsg3)
),
Expand Down
1 change: 1 addition & 0 deletions changes/ce/fix-10050.en.md
@@ -0,0 +1 @@
Ensure Bridge API returns `404` status code consistently for resources that don't exist.
1 change: 1 addition & 0 deletions changes/ce/fix-10050.zh.md
@@ -0,0 +1 @@
确保 Bridge API 对不存在的资源一致返回 `404` 状态代码。