diff --git a/src/tribler-core/tribler_core/components/libtorrent/restapi/create_torrent_endpoint.py b/src/tribler-core/tribler_core/components/libtorrent/restapi/create_torrent_endpoint.py index 442f4fac225..b0f0ad4e83b 100644 --- a/src/tribler-core/tribler_core/components/libtorrent/restapi/create_torrent_endpoint.py +++ b/src/tribler-core/tribler_core/components/libtorrent/restapi/create_torrent_endpoint.py @@ -69,7 +69,7 @@ async def create_torrent(self, request): if 'files' in parameters and parameters['files']: file_path_list = [ensure_unicode(f, 'utf-8') for f in parameters['files']] else: - return RESTResponse({"error": "files parameter missing"}, status=HTTP_BAD_REQUEST) + return self.bad_request("files parameter missing") if 'description' in parameters and parameters['description']: params['comment'] = parameters['description'] diff --git a/src/tribler-core/tribler_core/components/libtorrent/restapi/downloads_endpoint.py b/src/tribler-core/tribler_core/components/libtorrent/restapi/downloads_endpoint.py index a24fe29b893..f6b6ccbce35 100644 --- a/src/tribler-core/tribler_core/components/libtorrent/restapi/downloads_endpoint.py +++ b/src/tribler-core/tribler_core/components/libtorrent/restapi/downloads_endpoint.py @@ -17,9 +17,6 @@ from tribler_core.components.libtorrent.download_manager.stream import STREAM_PAUSE_TIME, StreamChunk from tribler_core.components.libtorrent.utils.libtorrent_helper import libtorrent as lt from tribler_core.components.restapi.rest.rest_endpoint import ( - HTTP_BAD_REQUEST, - HTTP_INTERNAL_SERVER_ERROR, - HTTP_NOT_FOUND, RESTEndpoint, RESTResponse, RESTStreamResponse, @@ -104,13 +101,6 @@ def setup_routes(self): web.get('/{infohash}/files', self.get_files), web.get('/{infohash}/stream/{fileindex}', self.stream, allow_head=False)]) - @staticmethod - def return_404(request, message="this download does not exist"): - """ - Returns a 404 response code if your channel has not been created. - """ - return RESTResponse({"error": message}, status=HTTP_NOT_FOUND) - @staticmethod def create_dconfig_from_params(parameters): """ @@ -376,16 +366,16 @@ async def add_download(self, request): params = await request.json() uri = params.get('uri') if not uri: - return RESTResponse({"error": "uri parameter missing"}, status=HTTP_BAD_REQUEST) + return self.bad_request("uri parameter missing") download_config, error = DownloadsEndpoint.create_dconfig_from_params(params) if error: - return RESTResponse({"error": error}, status=HTTP_BAD_REQUEST) + return self.bad_request(error) try: download = await self.download_manager.start_download_from_uri(uri, config=download_config) except Exception as e: - return RESTResponse({"error": str(e)}, status=HTTP_INTERNAL_SERVER_ERROR) + return self.internal_error(e) return RESTResponse({"started": True, "infohash": hexlify(download.get_def().get_infohash())}) @@ -412,12 +402,12 @@ async def add_download(self, request): async def delete_download(self, request): parameters = await request.json() if 'remove_data' not in parameters: - return RESTResponse({"error": "remove_data parameter missing"}, status=HTTP_BAD_REQUEST) + return self.bad_request(f"remove_data parameter missing", parameters=parameters) infohash = unhexlify(request.match_info['infohash']) download = self.download_manager.get_download(infohash) if not download: - return DownloadsEndpoint.return_404(request) + return self.not_found("download does not exist") try: await self.download_manager.remove_download(download, remove_content=parameters['remove_data']) @@ -432,8 +422,7 @@ async def vod_response(self, download, parameters, request, vod_mode): if vod_mode: file_index = parameters.get("fileindex") if file_index is None: - return RESTResponse({"error": "fileindex is necessary to enable vod_mode"}, - status=HTTP_BAD_REQUEST) + return self.bad_request(f"fileindex is necessary to enable vod_mode", parameters=parameters) if download.stream is None: download.add_stream() if not download.stream.enabled or download.stream.fileindex != file_index: @@ -480,19 +469,17 @@ async def update_download(self, request): infohash = unhexlify(request.match_info['infohash']) download = self.download_manager.get_download(infohash) if not download: - return DownloadsEndpoint.return_404(request) + return self.not_found("download does not exist") parameters = await request.json() vod_mode = parameters.get("vod_mode") if vod_mode is not None: if not isinstance(vod_mode, bool): - return RESTResponse({"error": "vod_mode must be bool flag"}, - status=HTTP_BAD_REQUEST) + return self.bad_request(f"vod_mode must be bool flag. Got: {vod_mode!r}") return await self.vod_response(download, parameters, request, vod_mode) if len(parameters) > 1 and 'anon_hops' in parameters: - return RESTResponse({"error": "anon_hops must be the only parameter in this request"}, - status=HTTP_BAD_REQUEST) + return self.bad_request(f"anon_hops must be the only parameter in request", parameters=parameters) elif 'anon_hops' in parameters: anon_hops = int(parameters['anon_hops']) try: @@ -503,11 +490,13 @@ async def update_download(self, request): return RESTResponse({"modified": True, "infohash": hexlify(download.get_def().get_infohash())}) if 'selected_files' in parameters: - selected_files_list = parameters['selected_files'] + selected_files = parameters['selected_files'] num_files = len(download.tdef.get_files()) - if not all([0 <= index < num_files for index in selected_files_list]): - return RESTResponse({"error": "index out of range"}, status=HTTP_BAD_REQUEST) - download.set_selected_files(selected_files_list) + for index in selected_files: + if not 0 <= index < num_files: + return self.bad_request(f"index of file out of range: {index}", num_files=num_files, + selected_files=selected_files) + download.set_selected_files(selected_files) if parameters.get('state'): state = parameters['state'] @@ -524,7 +513,7 @@ async def update_download(self, request): download.move_storage(dest_dir) download.checkpoint() else: - return RESTResponse({"error": "unknown state parameter"}, status=HTTP_BAD_REQUEST) + return self.bad_request(f"unknown state parameter: {state!r}") return RESTResponse({"modified": True, "infohash": hexlify(download.get_def().get_infohash())}) @@ -546,11 +535,11 @@ async def get_torrent(self, request): infohash = unhexlify(request.match_info['infohash']) download = self.download_manager.get_download(infohash) if not download: - return DownloadsEndpoint.return_404(request) + return self.not_found("download does not exist") torrent = download.get_torrent_data() if not torrent: - return DownloadsEndpoint.return_404(request) + return self.not_found("download does not exist") return RESTResponse(lt.bencode(torrent), headers={'content-type': 'application/x-bittorrent', 'Content-Disposition': 'attachment; filename=%s.torrent' @@ -580,7 +569,7 @@ async def get_files(self, request): infohash = unhexlify(request.match_info['infohash']) download = self.download_manager.get_download(infohash) if not download: - return DownloadsEndpoint.return_404(request) + return self.not_found("download does not exist") return RESTResponse({"files": self.get_files_info_json(download)}) @docs( @@ -608,7 +597,7 @@ async def stream(self, request): infohash = unhexlify(request.match_info['infohash']) download = self.download_manager.get_download(infohash) if not download: - return DownloadsEndpoint.return_404(request) + return self.not_found("download does not exist") file_index = int(request.match_info['fileindex']) diff --git a/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_downloads_endpoint.py b/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_downloads_endpoint.py index e728c6de6e1..3ff854da81e 100644 --- a/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_downloads_endpoint.py +++ b/src/tribler-core/tribler_core/components/libtorrent/restapi/tests/test_downloads_endpoint.py @@ -379,7 +379,7 @@ async def test_download_unknown_state(mock_dlmgr, test_download, rest_api): await do_request(rest_api, f'downloads/{test_download.infohash}', expected_code=400, post_data={"state": "abc"}, request_type='PATCH', - expected_json={"error": "unknown state parameter"}) + expected_json={"error": "unknown state parameter: 'abc'", "context": {}}) async def test_change_hops_error(mock_dlmgr, test_download, rest_api): diff --git a/src/tribler-core/tribler_core/components/libtorrent/restapi/torrentinfo_endpoint.py b/src/tribler-core/tribler_core/components/libtorrent/restapi/torrentinfo_endpoint.py index c06f1ef8497..0cd13f93902 100644 --- a/src/tribler-core/tribler_core/components/libtorrent/restapi/torrentinfo_endpoint.py +++ b/src/tribler-core/tribler_core/components/libtorrent/restapi/torrentinfo_endpoint.py @@ -15,8 +15,6 @@ from tribler_core.components.libtorrent.utils.libtorrent_helper import libtorrent as lt from tribler_core.components.metadata_store.db.orm_bindings.torrent_metadata import tdef_to_metadata_dict from tribler_core.components.restapi.rest.rest_endpoint import ( - HTTP_BAD_REQUEST, - HTTP_INTERNAL_SERVER_ERROR, RESTEndpoint, RESTResponse, ) @@ -82,10 +80,10 @@ async def get_torrent_info(self, request): try: hops = int(hops) except ValueError: - return RESTResponse({"error": f"wrong value of 'hops' parameter: {hops}"}, status=HTTP_BAD_REQUEST) + return self.bad_request(f"wrong value of 'hops' parameter: {hops}") if not uri: - return RESTResponse({"error": "uri parameter missing"}, status=HTTP_BAD_REQUEST) + return self.bad_request("uri parameter missing") metainfo = None scheme = scheme_from_uri(uri) @@ -95,14 +93,13 @@ async def get_torrent_info(self, request): try: tdef = TorrentDef.load(file) metainfo = tdef.metainfo - except (TypeError, RuntimeError): - return RESTResponse({"error": f"error while decoding torrent file: {file}"}, - status=HTTP_INTERNAL_SERVER_ERROR) + except (TypeError, RuntimeError) as e: + return self.internal_error(e, f"error while decoding torrent file: {file}") elif scheme in (HTTP_SCHEME, HTTPS_SCHEME): try: response = await query_http_uri(uri) except (ServerConnectionError, ClientResponseError) as e: - return RESTResponse({"error": str(e)}, status=HTTP_INTERNAL_SERVER_ERROR) + return self.internal_error(e) if response.startswith(b'magnet'): _, infohash, _ = parse_magnetlink(response) @@ -113,17 +110,17 @@ async def get_torrent_info(self, request): elif scheme == MAGNET_SCHEME: infohash = parse_magnetlink(uri)[1] if infohash is None: - return RESTResponse({"error": "missing infohash"}, status=HTTP_BAD_REQUEST) + return self.bad_request("missing infohash") metainfo = await self.download_manager.get_metainfo(infohash, timeout=60, hops=hops, url=uri) else: - return RESTResponse({"error": "invalid uri"}, status=HTTP_BAD_REQUEST) + return self.bad_request("invalid uri") if not metainfo: - return RESTResponse({"error": "metainfo error"}, status=HTTP_INTERNAL_SERVER_ERROR) + return self.internal_error(msg="metainfo error") if not isinstance(metainfo, dict) or b'info' not in metainfo: self._logger.warning("Received metainfo is not a valid dictionary") - return RESTResponse({"error": "invalid response"}, status=HTTP_INTERNAL_SERVER_ERROR) + return self.internal_error(msg="Received metainfo is not a valid dictionary") # Add the torrent to GigaChannel as a free-for-all entry, so others can search it self.download_manager.notifier.notify( diff --git a/src/tribler-core/tribler_core/components/metadata_store/restapi/channels_endpoint.py b/src/tribler-core/tribler_core/components/metadata_store/restapi/channels_endpoint.py index f2ad41f0f74..cc61a3ab724 100644 --- a/src/tribler-core/tribler_core/components/metadata_store/restapi/channels_endpoint.py +++ b/src/tribler-core/tribler_core/components/metadata_store/restapi/channels_endpoint.py @@ -285,16 +285,17 @@ async def copy_channel(self, request): try: request_parsed = await request.json() except (ContentTypeError, ValueError): - return RESTResponse({"error": "Bad JSON"}, status=HTTP_BAD_REQUEST) + return self.bad_request("Bad JSON") if not target_collection and not personal_root: - return RESTResponse({"error": "Target channel not found"}, status=HTTP_NOT_FOUND) + return self.not_found("Target channel not found") + results_list = [] for entry in request_parsed: public_key, id_ = unhexlify(entry["public_key"]), entry["id"] source = self.mds.ChannelNode.get(public_key=public_key, id_=id_) if not source: - return RESTResponse({"error": "Source entry not found"}, status=HTTP_BAD_REQUEST) + return self.bad_request("Source entry not found") # We must upgrade Collections to Channels when moving them to root channel, and, vice-versa, # downgrade Channels to Collections when moving them into existing channels if isinstance(source, self.mds.CollectionNode): @@ -379,7 +380,7 @@ async def add_torrent_to_channel(self, request): with db_session: channel = self.mds.CollectionNode.get(public_key=channel_pk, id_=channel_id) if not channel: - return RESTResponse({"error": "Unknown channel"}, status=HTTP_NOT_FOUND) + return self.not_found("Unknown channel") parameters = await request.json() @@ -407,7 +408,7 @@ async def add_torrent_to_channel(self, request): raise RuntimeError("Metainfo timeout") tdef = TorrentDef.load_from_dict(meta_info) else: - return RESTResponse({"error": "unknown uri type"}, status=HTTP_BAD_REQUEST) + return self.bad_request("unknown uri type") added = 0 if tdef: @@ -419,23 +420,21 @@ async def add_torrent_to_channel(self, request): if parameters.get('torrents_dir', None): torrents_dir = parameters['torrents_dir'] if not Path(torrents_dir).is_absolute(): - return RESTResponse({"error": "the torrents_dir should point to a directory"}, status=HTTP_BAD_REQUEST) + return self.bad_request("the torrents_dir should point to a directory") recursive = False if parameters.get('recursive'): recursive = parameters['recursive'] if not torrents_dir: - return RESTResponse( - {"error": "the torrents_dir parameter should be provided when the recursive parameter is set"}, - status=HTTP_BAD_REQUEST, - ) + return self.bad_request( + "the torrents_dir parameter should be provided when the recursive parameter is set") if torrents_dir: torrents_list, errors_list = channel.add_torrents_from_dir(torrents_dir, recursive) return RESTResponse({"added": len(torrents_list), "errors": errors_list}) if not parameters.get('torrent', None): - return RESTResponse({"error": "torrent parameter missing"}, status=HTTP_BAD_REQUEST) + return self.bad_request("torrent parameter missing") # Try to parse the torrent data # Any errors will be handled by the error_middleware diff --git a/src/tribler-core/tribler_core/components/metadata_store/restapi/metadata_endpoint.py b/src/tribler-core/tribler_core/components/metadata_store/restapi/metadata_endpoint.py index 6374565d697..3a84551cf97 100644 --- a/src/tribler-core/tribler_core/components/metadata_store/restapi/metadata_endpoint.py +++ b/src/tribler-core/tribler_core/components/metadata_store/restapi/metadata_endpoint.py @@ -1,4 +1,5 @@ from binascii import unhexlify +from typing import Optional, Tuple from aiohttp import ContentTypeError, web @@ -22,30 +23,8 @@ TORRENT_CHECK_TIMEOUT = 20 -class UpdateEntryMixin: - @db_session - def update_entry(self, public_key, id_, update_dict): - entry = self.mds.ChannelNode.get(public_key=public_key, id_=id_) - if not entry: - return HTTP_NOT_FOUND, {"error": "Object with the specified pk+id could not be found."} - - signed_parameters_to_change = set(entry.payload_arguments).intersection(set(update_dict.keys())) - if signed_parameters_to_change: - if 'status' in update_dict: - return HTTP_BAD_REQUEST, {"error": "Cannot set status manually when changing signed attributes."} - if entry.status == LEGACY_ENTRY: - return HTTP_BAD_REQUEST, {"error": "Changing parameters of legacy entries is not supported."} - if not entry.is_personal: - return ( - HTTP_BAD_REQUEST, - {"error": "Changing signed parameters in non-personal entries is not supported."}, - ) - - return None, entry.update_properties(update_dict).to_simple_dict() - - @froze_it -class MetadataEndpoint(MetadataEndpointBase, UpdateEntryMixin): +class MetadataEndpoint(MetadataEndpointBase): """ This is the top-level endpoint class that serves other endpoints. @@ -91,15 +70,15 @@ async def update_channel_entries(self, request): try: request_parsed = await request.json() except (ContentTypeError, ValueError): - return RESTResponse({"error": "Bad JSON"}, status=HTTP_BAD_REQUEST) + return self.bad_request("Bad JSON") results_list = [] for entry in request_parsed: public_key = unhexlify(entry.pop("public_key")) id_ = entry.pop("id") - error, result = self.update_entry(public_key, id_, entry) + error_response, result = self.update_entry(public_key, id_, entry) # TODO: handle the results for a list that contains some errors in a smarter way - if error: - return RESTResponse(result, status=error) + if error_response is not None: + return error_response results_list.append(result) return RESTResponse(results_list) @@ -129,7 +108,7 @@ async def delete_channel_entries(self, request): id_ = entry.pop("id") entry = self.mds.ChannelNode.get(public_key=public_key, id_=id_) if not entry: - return RESTResponse({"error": "Entry %i not found" % id_}, status=HTTP_BAD_REQUEST) + return self.bad_request("Entry %i not found" % id_) entry.delete() result = {"public_key": hexlify(public_key), "id": id_, "state": "Deleted"} results_list.append(result) @@ -149,12 +128,34 @@ async def update_channel_entry(self, request): try: parameters = await request.json() except (ContentTypeError, ValueError): - return RESTResponse({"error": "Bad JSON input data"}, status=HTTP_BAD_REQUEST) + return self.bad_request("Bad JSON input data") public_key = unhexlify(request.match_info['public_key']) id_ = request.match_info['id'] - error, result = self.update_entry(public_key, id_, parameters) - return RESTResponse(result, status=error or 200) + error_response, result = self.update_entry(public_key, id_, parameters) + if error_response is not None: + return error_response + return RESTResponse(result) + + @db_session + def update_entry(self, public_key, id_, parameters) -> Tuple[Optional[RESTResponse], Optional[dict]]: + entry = self.mds.ChannelNode.get(public_key=public_key, id_=id_) + if not entry: + return self.not_found("Object with the specified pk+id could not be found"), None + + signed_parameters_to_change = set(entry.payload_arguments).intersection(set(parameters.keys())) + if signed_parameters_to_change: + if 'status' in parameters: + return self.bad_request("Cannot set status manually when changing signed attributes"), None + + if entry.status == LEGACY_ENTRY: + return self.bad_request("Changing parameters of legacy entries is not supported"), None + + if not entry.is_personal: + return self.bad_request("Changing signed parameters in non-personal entries is not supported"), None + + result = entry.update_properties(parameters).to_simple_dict() + return None, result @docs( tags=['Metadata'], @@ -171,7 +172,7 @@ async def get_channel_entries(self, request): # TODO: handle costly attributes in a more graceful and generic way for all types of metadata entry_dict = entry.to_simple_dict() else: - return RESTResponse({"error": "entry not found in database"}, status=HTTP_NOT_FOUND) + return self.not_found("entry not found in database") return RESTResponse(entry_dict) @@ -245,7 +246,7 @@ async def get_torrent_health(self, request): elif timeout.isdigit(): timeout = int(timeout) else: - return RESTResponse({"error": f"Error processing timeout parameter '{timeout}'"}, status=HTTP_BAD_REQUEST) + return self.bad_request(f"Error processing timeout parameter '{timeout}'") refresh = request.query.get('refresh', '0') == '1' nowait = request.query.get('nowait', '0') == '1' diff --git a/src/tribler-core/tribler_core/components/metadata_store/restapi/remote_query_endpoint.py b/src/tribler-core/tribler_core/components/metadata_store/restapi/remote_query_endpoint.py index 56c3fac2e32..4cf05b4a145 100644 --- a/src/tribler-core/tribler_core/components/metadata_store/restapi/remote_query_endpoint.py +++ b/src/tribler-core/tribler_core/components/metadata_store/restapi/remote_query_endpoint.py @@ -14,7 +14,7 @@ from tribler_core.components.gigachannel.community.gigachannel_community import GigaChannelCommunity from tribler_core.components.metadata_store.restapi.metadata_endpoint import MetadataEndpointBase from tribler_core.components.metadata_store.restapi.metadata_schema import RemoteQueryParameters -from tribler_core.components.restapi.rest.rest_endpoint import HTTP_BAD_REQUEST, RESTResponse +from tribler_core.components.restapi.rest.rest_endpoint import RESTResponse from tribler_core.utilities.unicode import hexlify from tribler_core.utilities.utilities import froze_it @@ -56,7 +56,7 @@ async def create_remote_search_request(self, request): try: sanitized = self.sanitize_parameters(request.query) except (ValueError, KeyError) as e: - return RESTResponse({"error": f"Error processing request parameters: {e}"}, status=HTTP_BAD_REQUEST) + return self.bad_request(f"Error processing request parameters: {e}") self._logger.info(f'Parameters: {sanitized}') request_uuid, peers_list = self.gigachannel_community.send_search_request(**sanitized) diff --git a/src/tribler-core/tribler_core/components/metadata_store/restapi/search_endpoint.py b/src/tribler-core/tribler_core/components/metadata_store/restapi/search_endpoint.py index 6ba6df171df..5d47edb505a 100644 --- a/src/tribler-core/tribler_core/components/metadata_store/restapi/search_endpoint.py +++ b/src/tribler-core/tribler_core/components/metadata_store/restapi/search_endpoint.py @@ -11,7 +11,7 @@ from tribler_core.components.metadata_store.db.store import MetadataStore from tribler_core.components.metadata_store.restapi.metadata_endpoint import MetadataEndpointBase from tribler_core.components.metadata_store.restapi.metadata_schema import MetadataParameters, MetadataSchema -from tribler_core.components.restapi.rest.rest_endpoint import HTTP_BAD_REQUEST, RESTResponse +from tribler_core.components.restapi.rest.rest_endpoint import RESTResponse from tribler_core.utilities.utilities import froze_it @@ -55,7 +55,7 @@ async def search(self, request): sanitized = self.sanitize_parameters(request.query) tags = sanitized.pop('tags', None) except (ValueError, KeyError): - return RESTResponse({"error": "Error processing request parameters"}, status=HTTP_BAD_REQUEST) + return self.bad_request("Error processing request parameters") include_total = request.query.get('include_total', '') @@ -81,7 +81,7 @@ def search_db(): search_results, total, max_rowid = await mds.run_threaded(search_db) except Exception as e: # pylint: disable=broad-except; # pragma: no cover self._logger.exception("Error while performing DB search: %s: %s", type(e).__name__, e) - return RESTResponse(status=HTTP_BAD_REQUEST) + return self.bad_request("Error while performing DB search: %s: %s") self.add_tags_to_metadata_list(search_results, hide_xxx=sanitized["hide_xxx"]) @@ -115,7 +115,7 @@ def search_db(): async def completions(self, request): args = request.query if 'q' not in args: - return RESTResponse({"error": "query parameter missing"}, status=HTTP_BAD_REQUEST) + return self.bad_request("query parameter missing") keywords = args['q'].strip().lower() # TODO: add XXX filtering for completion terms diff --git a/src/tribler-core/tribler_core/components/restapi/rest/rest_endpoint.py b/src/tribler-core/tribler_core/components/restapi/rest/rest_endpoint.py index c471bfbc1e3..6625c2f2a87 100644 --- a/src/tribler-core/tribler_core/components/restapi/rest/rest_endpoint.py +++ b/src/tribler-core/tribler_core/components/restapi/rest/rest_endpoint.py @@ -24,6 +24,19 @@ def add_endpoint(self, prefix, endpoint): self.endpoints[prefix] = endpoint self.app.add_subapp(prefix, endpoint.app) + def bad_request(self, msg: str, status: int = HTTP_BAD_REQUEST, exc_info=None, **kwargs): + logger_msg = msg + if kwargs: + logger_msg += f', context: {kwargs!r}' + self._logger.error(logger_msg, exc_info=exc_info) + return RESTResponse({"error": msg, "context": kwargs}, status=status) + + def not_found(self, msg): + return self.bad_request(msg, status=HTTP_NOT_FOUND) + + def internal_error(self, exc: Exception = None, msg: str = None, **kwargs): + return self.bad_request(msg or str(exc), status=HTTP_INTERNAL_SERVER_ERROR, exc_info=exc, **kwargs) + class RESTResponse(web.Response): diff --git a/src/tribler-core/tribler_core/components/restapi/rest/schema.py b/src/tribler-core/tribler_core/components/restapi/rest/schema.py index 6e2d8079abc..6e52bca3a7c 100644 --- a/src/tribler-core/tribler_core/components/restapi/rest/schema.py +++ b/src/tribler-core/tribler_core/components/restapi/rest/schema.py @@ -1,8 +1,8 @@ -from ipv8.REST.schema import schema - from marshmallow import Schema -from marshmallow.fields import Boolean, Integer, String +from marshmallow.fields import Dict, String class HandledErrorSchema(Schema): error = String(description='Optional field describing any failures that may have occurred', required=True) + context = Dict(description='Arbitrary dict of additional data useful to understanding the error', + keys=String(), required=False) diff --git a/src/tribler-core/tribler_core/components/tag/restapi/tags_endpoint.py b/src/tribler-core/tribler_core/components/tag/restapi/tags_endpoint.py index 52a06b7717b..d53e9126ec4 100644 --- a/src/tribler-core/tribler_core/components/tag/restapi/tags_endpoint.py +++ b/src/tribler-core/tribler_core/components/tag/restapi/tags_endpoint.py @@ -32,14 +32,13 @@ def __init__(self, db: TagDatabase, community: TagCommunity): self.db: TagDatabase = db self.community: TagCommunity = community - @staticmethod - def validate_infohash(infohash: str) -> Tuple[bool, Optional[RESTResponse]]: + def validate_infohash(self, infohash: str) -> Tuple[bool, Optional[RESTResponse]]: try: infohash = unhexlify(infohash) if len(infohash) != 20: - return False, RESTResponse({"error": "Invalid infohash"}, status=HTTP_BAD_REQUEST) + return False, self.bad_request("Invalid infohash") except binascii.Error: - return False, RESTResponse({"error": "Invalid infohash"}, status=HTTP_BAD_REQUEST) + return False, self.bad_request("Invalid infohash") return True, None @@ -66,7 +65,7 @@ def setup_routes(self): async def update_tags_entries(self, request): params = await request.json() infohash = request.match_info["infohash"] - ih_valid, error_response = TagsEndpoint.validate_infohash(infohash) + ih_valid, error_response = self.validate_infohash(infohash) if not ih_valid: return error_response @@ -75,7 +74,7 @@ async def update_tags_entries(self, request): # Validate whether the size of the tag is within the allowed range for tag in tags: if len(tag) < MIN_TAG_LENGTH or len(tag) > MAX_TAG_LENGTH: - return RESTResponse({"error": "Invalid tag length"}, status=HTTP_BAD_REQUEST) + return self.bad_request("Invalid tag length") self.modify_tags(unhexlify(infohash), tags) @@ -121,7 +120,7 @@ async def get_suggestions(self, request): Get suggestions for a particular tag. """ infohash = request.match_info["infohash"] - ih_valid, error_response = TagsEndpoint.validate_infohash(infohash) + ih_valid, error_response = self.validate_infohash(infohash) if not ih_valid: return error_response diff --git a/src/tribler-gui/tribler_gui/tribler_request_manager.py b/src/tribler-gui/tribler_gui/tribler_request_manager.py index a587147bdd9..d435a94953e 100644 --- a/src/tribler-gui/tribler_gui/tribler_request_manager.py +++ b/src/tribler-gui/tribler_gui/tribler_request_manager.py @@ -62,15 +62,19 @@ def get_base_url(self): @staticmethod def get_message_from_error(error): - return_error = None + result = None if isinstance(error['error'], str): - return_error = error['error'] + result = error['error'] elif 'message' in error['error']: - return_error = error['error']['message'] + result = error['error']['message'] - if not return_error: + if not result: return json.dumps(error) # Just print the json object - return return_error + + if 'context' in error and isinstance(error['context'], dict): + result += '\n\n' + '\n'.join(f'{key}={value!r}' for key, value in sorted(error['context'].items())) + + return result def show_error(self, error_text): main_text = f"An error occurred during the request:\n\n{error_text}"