From 7e878cdbf86c924ea3e14d4966e3b4407e804c2a Mon Sep 17 00:00:00 2001 From: Petr Mironychev <9195189+Palm1r@users.noreply.github.com> Date: Tue, 21 Oct 2025 00:51:42 +0200 Subject: [PATCH] fix: Add tool exception for logging purpose --- tools/EditProjectFileTool.cpp | 33 +++++++++------- tools/FindFileTool.cpp | 3 +- tools/FindSymbolTool.cpp | 5 ++- tools/ListProjectFilesTool.cpp | 3 +- tools/ReadFileByPathTool.cpp | 41 ++++++++++++++------ tools/ReadVisibleFilesTool.cpp | 5 ++- tools/SearchInProjectTool.cpp | 3 +- tools/ToolExceptions.hpp | 69 ++++++++++++++++++++++++++++++++++ tools/ToolHandler.cpp | 36 ++++++++++++++++++ 9 files changed, 165 insertions(+), 33 deletions(-) create mode 100644 tools/ToolExceptions.hpp diff --git a/tools/EditProjectFileTool.cpp b/tools/EditProjectFileTool.cpp index b242314..53873be 100644 --- a/tools/EditProjectFileTool.cpp +++ b/tools/EditProjectFileTool.cpp @@ -18,6 +18,7 @@ */ #include "EditProjectFileTool.hpp" +#include "ToolExceptions.hpp" #include #include @@ -133,13 +134,13 @@ QFuture EditProjectFileTool::executeAsync(const QJsonObject &input) QString filename = input["filename"].toString(); if (filename.isEmpty()) { QString error = "Error: filename parameter is required"; - throw std::invalid_argument(error.toStdString()); + throw ToolInvalidArgument(error); } QString modeStr = input["mode"].toString(); if (modeStr.isEmpty()) { QString error = "Error: mode parameter is required"; - throw std::invalid_argument(error.toStdString()); + throw ToolInvalidArgument(error); } EditMode mode; @@ -155,32 +156,32 @@ QFuture EditProjectFileTool::executeAsync(const QJsonObject &input) QString error = QString("Error: Invalid mode '%1'. Must be one of: replace, " "insert_before, insert_after, append") .arg(modeStr); - throw std::invalid_argument(error.toStdString()); + throw ToolInvalidArgument(error); } QString newText = input["new_text"].toString(); if (newText.isEmpty()) { QString error = "Error: new_text parameter is required"; - throw std::invalid_argument(error.toStdString()); + throw ToolInvalidArgument(error); } QString searchText = input["search_text"].toString(); if (mode == EditMode::Replace && searchText.isEmpty()) { QString error = "Error: search_text parameter is required for replace mode"; - throw std::invalid_argument(error.toStdString()); + throw ToolInvalidArgument(error); } int lineNumber = input["line_number"].toInt(0); if ((mode == EditMode::InsertBefore || mode == EditMode::InsertAfter) && lineNumber <= 0) { QString error = "Error: line_number parameter is required for insert modes and must " "be greater than 0"; - throw std::invalid_argument(error.toStdString()); + throw ToolInvalidArgument(error); } QString filePath = findFileInProject(filename); if (filePath.isEmpty()) { QString error = QString("Error: File '%1' not found in project").arg(filename); - throw std::runtime_error(error.toStdString()); + throw ToolRuntimeError(error); } auto project = ProjectExplorer::ProjectManager::projectForFile( @@ -189,14 +190,11 @@ QFuture EditProjectFileTool::executeAsync(const QJsonObject &input) QString error = QString("Error: File '%1' is excluded by .qodeassistignore and cannot be edited") .arg(filename); - throw std::runtime_error(error.toStdString()); + throw ToolRuntimeError(error); } + // readFileContent throws exception if file cannot be opened QString originalContent = readFileContent(filePath); - if (originalContent.isNull()) { - QString error = QString("Error: Could not read file '%1'").arg(filePath); - throw std::runtime_error(error.toStdString()); - } LOG_MESSAGE(QString("Prepared file edit: %1 (mode: %2)").arg(filePath, modeStr)); @@ -282,14 +280,21 @@ QString EditProjectFileTool::readFileContent(const QString &filePath) const { QFile file(filePath); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - LOG_MESSAGE(QString("Could not open file for reading: %1").arg(filePath)); - return QString(); + LOG_MESSAGE(QString("Could not open file for reading: %1, error: %2") + .arg(filePath, file.errorString())); + throw ToolRuntimeError(QString("Error: Could not open file '%1': %2") + .arg(filePath, file.errorString())); } QTextStream stream(&file); stream.setAutoDetectUnicode(true); QString content = stream.readAll(); file.close(); + + LOG_MESSAGE(QString("Successfully read file for edit: %1, size: %2 bytes") + .arg(filePath) + .arg(content.length())); + return content; } diff --git a/tools/FindFileTool.cpp b/tools/FindFileTool.cpp index 0ba80f9..623c38b 100644 --- a/tools/FindFileTool.cpp +++ b/tools/FindFileTool.cpp @@ -18,6 +18,7 @@ */ #include "FindFileTool.hpp" +#include "ToolExceptions.hpp" #include #include @@ -113,7 +114,7 @@ QFuture FindFileTool::executeAsync(const QJsonObject &input) QString query = input["query"].toString().trimmed(); if (query.isEmpty()) { QString error = "Error: query parameter is required and cannot be empty"; - throw std::invalid_argument(error.toStdString()); + throw ToolInvalidArgument(error); } QString filePattern = input["file_pattern"].toString().trimmed(); diff --git a/tools/FindSymbolTool.cpp b/tools/FindSymbolTool.cpp index b30b2cb..f3028fa 100644 --- a/tools/FindSymbolTool.cpp +++ b/tools/FindSymbolTool.cpp @@ -18,6 +18,7 @@ */ #include "FindSymbolTool.hpp" +#include "ToolExceptions.hpp" #include #include @@ -157,13 +158,13 @@ QFuture FindSymbolTool::executeAsync(const QJsonObject &input) if (symbolName.isEmpty()) { QString error = "Error: 'symbol_name' parameter is required"; LOG_MESSAGE(error); - throw std::invalid_argument(error.toStdString()); + throw ToolInvalidArgument(error); } if (useRegex && useWildcard) { QString error = "Error: 'use_regex' and 'use_wildcard' cannot be used together"; LOG_MESSAGE(error); - throw std::invalid_argument(error.toStdString()); + throw ToolInvalidArgument(error); } SymbolType type = parseSymbolType(symbolTypeStr); diff --git a/tools/ListProjectFilesTool.cpp b/tools/ListProjectFilesTool.cpp index 16e8c78..4ad2d28 100644 --- a/tools/ListProjectFilesTool.cpp +++ b/tools/ListProjectFilesTool.cpp @@ -18,6 +18,7 @@ */ #include "ListProjectFilesTool.hpp" +#include "ToolExceptions.hpp" #include #include @@ -86,7 +87,7 @@ QFuture ListProjectFilesTool::executeAsync(const QJsonObject &input) QList projects = ProjectExplorer::ProjectManager::projects(); if (projects.isEmpty()) { QString error = "Error: No projects found"; - throw std::runtime_error(error.toStdString()); + throw ToolRuntimeError(error); } QString result; diff --git a/tools/ReadFileByPathTool.cpp b/tools/ReadFileByPathTool.cpp index 8f3555e..629dee5 100644 --- a/tools/ReadFileByPathTool.cpp +++ b/tools/ReadFileByPathTool.cpp @@ -18,6 +18,7 @@ */ #include "ReadFileByPathTool.hpp" +#include "ToolExceptions.hpp" #include #include @@ -97,16 +98,22 @@ QFuture ReadProjectFileByPathTool::executeAsync(const QJsonObject &inpu QString filePath = input["filepath"].toString(); if (filePath.isEmpty()) { QString error = "Error: filepath parameter is required"; - throw std::invalid_argument(error.toStdString()); + throw ToolInvalidArgument(error); } QFileInfo fileInfo(filePath); + LOG_MESSAGE(QString("Checking file: %1, exists: %2, isFile: %3") + .arg(filePath) + .arg(fileInfo.exists()) + .arg(fileInfo.isFile())); + if (!fileInfo.exists() || !fileInfo.isFile()) { QString error = QString("Error: File '%1' does not exist").arg(filePath); - throw std::runtime_error(error.toStdString()); + throw ToolRuntimeError(error); } QString canonicalPath = fileInfo.canonicalFilePath(); + LOG_MESSAGE(QString("Canonical path: %1").arg(canonicalPath)); bool isInProject = isFileInProject(canonicalPath); @@ -117,7 +124,7 @@ QFuture ReadProjectFileByPathTool::executeAsync(const QJsonObject &inpu QString error = QString("Error: File '%1' is not part of the project. " "Enable 'Allow reading files outside project' in settings to access this file.") .arg(filePath); - throw std::runtime_error(error.toStdString()); + throw ToolRuntimeError(error); } LOG_MESSAGE(QString("Reading file outside project scope: %1").arg(canonicalPath)); } @@ -127,17 +134,19 @@ QFuture ReadProjectFileByPathTool::executeAsync(const QJsonObject &inpu if (isInProject && project && m_ignoreManager->shouldIgnore(canonicalPath, project)) { QString error = QString("Error: File '%1' is excluded by .qodeassistignore").arg(filePath); - throw std::runtime_error(error.toStdString()); + throw ToolRuntimeError(error); } + // readFileContent throws exception if file cannot be opened + // If it returns, the file was read successfully (may be empty) QString content = readFileContent(canonicalPath); - if (content.isNull()) { - QString error = QString("Error: Could not read file '%1'").arg(canonicalPath); - throw std::runtime_error(error.toStdString()); - } - QString result = QString("File: %1\n\nContent:\n%2").arg(canonicalPath, content); - return result; + // Return appropriate message for empty or non-empty files + if (content.isEmpty()) { + return QString("File: %1\n\nThe file is empty").arg(canonicalPath); + } + + return QString("File: %1\n\nContent:\n%2").arg(canonicalPath, content); }); } @@ -170,8 +179,9 @@ QString ReadProjectFileByPathTool::readFileContent(const QString &filePath) cons { QFile file(filePath); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - LOG_MESSAGE(QString("Could not open file: %1").arg(filePath)); - return QString(); + LOG_MESSAGE(QString("Could not open file: %1, error: %2").arg(filePath, file.errorString())); + throw ToolRuntimeError(QString("Error: Could not open file '%1': %2") + .arg(filePath, file.errorString())); } QTextStream stream(&file); @@ -179,6 +189,13 @@ QString ReadProjectFileByPathTool::readFileContent(const QString &filePath) cons QString content = stream.readAll(); file.close(); + + LOG_MESSAGE(QString("Successfully read file: %1, size: %2 bytes, isEmpty: %3") + .arg(filePath) + .arg(content.length()) + .arg(content.isEmpty())); + + // Always return valid QString (empty string for empty files) return content; } diff --git a/tools/ReadVisibleFilesTool.cpp b/tools/ReadVisibleFilesTool.cpp index d0b960f..8f42986 100644 --- a/tools/ReadVisibleFilesTool.cpp +++ b/tools/ReadVisibleFilesTool.cpp @@ -18,6 +18,7 @@ */ #include "ReadVisibleFilesTool.hpp" +#include "ToolExceptions.hpp" #include #include @@ -85,7 +86,7 @@ QFuture ReadVisibleFilesTool::executeAsync(const QJsonObject &input) if (editors.isEmpty()) { QString error = "Error: No visible files in the editor"; - throw std::runtime_error(error.toStdString()); + throw ToolRuntimeError(error); } QStringList results; @@ -121,7 +122,7 @@ QFuture ReadVisibleFilesTool::executeAsync(const QJsonObject &input) if (results.isEmpty()) { QString error = "Error: All visible files are excluded by .qodeassistignore"; - throw std::runtime_error(error.toStdString()); + throw ToolRuntimeError(error); } return results.join("\n\n" + QString(80, '=') + "\n\n"); diff --git a/tools/SearchInProjectTool.cpp b/tools/SearchInProjectTool.cpp index f76d9d4..84935ae 100644 --- a/tools/SearchInProjectTool.cpp +++ b/tools/SearchInProjectTool.cpp @@ -18,6 +18,7 @@ */ #include "SearchInProjectTool.hpp" +#include "ToolExceptions.hpp" #include #include @@ -122,7 +123,7 @@ QFuture SearchInProjectTool::executeAsync(const QJsonObject &input) QString query = input["query"].toString(); if (query.isEmpty()) { QString error = "Error: query parameter is required"; - throw std::invalid_argument(error.toStdString()); + throw ToolInvalidArgument(error); } bool caseSensitive = input["case_sensitive"].toBool(false); diff --git a/tools/ToolExceptions.hpp b/tools/ToolExceptions.hpp new file mode 100644 index 0000000..217e80b --- /dev/null +++ b/tools/ToolExceptions.hpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2025 Petr Mironychev + * + * This file is part of QodeAssist. + * + * QodeAssist is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QodeAssist is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QodeAssist. If not, see . + */ + +#pragma once + +#include +#include + +namespace QodeAssist::Tools { + +class ToolException : public QException +{ +public: + explicit ToolException(const QString &message) + : m_message(message) + , m_stdMessage(message.toStdString()) + {} + + void raise() const override { throw *this; } + ToolException *clone() const override { return new ToolException(*this); } + const char *what() const noexcept override { return m_stdMessage.c_str(); } + + QString message() const { return m_message; } + +private: + QString m_message; + std::string m_stdMessage; +}; + +class ToolRuntimeError : public ToolException +{ +public: + explicit ToolRuntimeError(const QString &message) + : ToolException(message) + {} + + void raise() const override { throw *this; } + ToolRuntimeError *clone() const override { return new ToolRuntimeError(*this); } +}; + +class ToolInvalidArgument : public ToolException +{ +public: + explicit ToolInvalidArgument(const QString &message) + : ToolException(message) + {} + + void raise() const override { throw *this; } + ToolInvalidArgument *clone() const override { return new ToolInvalidArgument(*this); } +}; + +} // namespace QodeAssist::Tools + diff --git a/tools/ToolHandler.cpp b/tools/ToolHandler.cpp index a6168c3..93b0c50 100644 --- a/tools/ToolHandler.cpp +++ b/tools/ToolHandler.cpp @@ -18,6 +18,7 @@ */ #include "ToolHandler.hpp" +#include "ToolExceptions.hpp" #include #include @@ -93,8 +94,43 @@ void ToolHandler::onToolExecutionFinished(const QString &toolId) QString result = execution->watcher->result(); LOG_MESSAGE(QString("Tool %1 completed").arg(execution->toolName)); emit toolCompleted(execution->requestId, execution->toolId, result); + } catch (const ToolException &e) { + QString error = e.message(); + if (error.isEmpty()) { + error = "Tool execution failed with empty error message"; + } + LOG_MESSAGE(QString("Tool %1 failed: %2").arg(execution->toolName, error)); + emit toolFailed(execution->requestId, execution->toolId, error); + } catch (const QException &e) { + QString error = QString::fromUtf8(e.what()); + if (error.isEmpty()) { + error = "Tool execution failed (QException with empty message)"; + } + LOG_MESSAGE(QString("Tool %1 failed: %2").arg(execution->toolName, error)); + emit toolFailed(execution->requestId, execution->toolId, error); + } catch (const std::runtime_error &e) { + QString error = QString::fromStdString(e.what()); + if (error.isEmpty()) { + error = "Unknown runtime error occurred"; + } + LOG_MESSAGE(QString("Tool %1 failed: %2").arg(execution->toolName, error)); + emit toolFailed(execution->requestId, execution->toolId, error); + } catch (const std::invalid_argument &e) { + QString error = QString::fromStdString(e.what()); + if (error.isEmpty()) { + error = "Invalid argument provided"; + } + LOG_MESSAGE(QString("Tool %1 failed: %2").arg(execution->toolName, error)); + emit toolFailed(execution->requestId, execution->toolId, error); } catch (const std::exception &e) { QString error = QString::fromStdString(e.what()); + if (error.isEmpty()) { + error = "Unknown exception occurred"; + } + LOG_MESSAGE(QString("Tool %1 failed: %2").arg(execution->toolName, error)); + emit toolFailed(execution->requestId, execution->toolId, error); + } catch (...) { + QString error = "Unknown error occurred during tool execution"; LOG_MESSAGE(QString("Tool %1 failed: %2").arg(execution->toolName, error)); emit toolFailed(execution->requestId, execution->toolId, error); }