fix: Add tool exception for logging purpose

This commit is contained in:
Petr Mironychev
2025-10-21 00:51:42 +02:00
parent c95b20d6d4
commit 7e878cdbf8
9 changed files with 165 additions and 33 deletions

View File

@ -18,6 +18,7 @@
*/
#include "EditProjectFileTool.hpp"
#include "ToolExceptions.hpp"
#include <coreplugin/documentmanager.h>
#include <logger/Logger.hpp>
@ -133,13 +134,13 @@ QFuture<QString> 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<QString> 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<QString> 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;
}

View File

@ -18,6 +18,7 @@
*/
#include "FindFileTool.hpp"
#include "ToolExceptions.hpp"
#include <logger/Logger.hpp>
#include <projectexplorer/project.h>
@ -113,7 +114,7 @@ QFuture<QString> 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();

View File

@ -18,6 +18,7 @@
*/
#include "FindSymbolTool.hpp"
#include "ToolExceptions.hpp"
#include <cplusplus/Overview.h>
#include <cplusplus/Scope.h>
@ -157,13 +158,13 @@ QFuture<QString> 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);

View File

@ -18,6 +18,7 @@
*/
#include "ListProjectFilesTool.hpp"
#include "ToolExceptions.hpp"
#include <logger/Logger.hpp>
#include <projectexplorer/project.h>
@ -86,7 +87,7 @@ QFuture<QString> ListProjectFilesTool::executeAsync(const QJsonObject &input)
QList<ProjectExplorer::Project *> projects = ProjectExplorer::ProjectManager::projects();
if (projects.isEmpty()) {
QString error = "Error: No projects found";
throw std::runtime_error(error.toStdString());
throw ToolRuntimeError(error);
}
QString result;

View File

@ -18,6 +18,7 @@
*/
#include "ReadFileByPathTool.hpp"
#include "ToolExceptions.hpp"
#include <coreplugin/documentmanager.h>
#include <logger/Logger.hpp>
@ -97,16 +98,22 @@ QFuture<QString> 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<QString> 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<QString> 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;
}

View File

@ -18,6 +18,7 @@
*/
#include "ReadVisibleFilesTool.hpp"
#include "ToolExceptions.hpp"
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
@ -85,7 +86,7 @@ QFuture<QString> 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<QString> 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");

View File

@ -18,6 +18,7 @@
*/
#include "SearchInProjectTool.hpp"
#include "ToolExceptions.hpp"
#include <logger/Logger.hpp>
#include <projectexplorer/project.h>
@ -122,7 +123,7 @@ QFuture<QString> 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);

69
tools/ToolExceptions.hpp Normal file
View File

@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QException>
#include <QString>
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

View File

@ -18,6 +18,7 @@
*/
#include "ToolHandler.hpp"
#include "ToolExceptions.hpp"
#include <QJsonDocument>
#include <QTimer>
@ -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);
}