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:
commit
9fb74bfc87
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
),
|
),
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Ensure Bridge API returns `404` status code consistently for resources that don't exist.
|
|
@ -0,0 +1 @@
|
||||||
|
确保 Bridge API 对不存在的资源一致返回 `404` 状态代码。
|
Loading…
Reference in New Issue