diff --git a/src/blackmisc/jsonexception.cpp b/src/blackmisc/jsonexception.cpp new file mode 100644 index 000000000..b97d4a0fe --- /dev/null +++ b/src/blackmisc/jsonexception.cpp @@ -0,0 +1,58 @@ +/* Copyright (C) 2016 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +#include "blackmisc/jsonexception.h" +#include "blackmisc/statusmessage.h" +#include "blackmisc/logcategorylist.h" +#include "blackmisc/logmessage.h" +#include +#include +#include + +namespace BlackMisc +{ + //! \private + auto &jsonStack() noexcept + { + static QThreadStorage> stack; + return stack.localData(); + } + + CStatusMessage CJsonException::toStatusMessage(const CLogCategoryList &categories, const QString &prefix) const + { + return CStatusMessage(categories).validationError("%1: %2 in '%3'") << prefix << what() << getStackTrace(); + } + + void CJsonException::toLogMessage(const CLogCategoryList &categories, const QString &prefix) const + { + CLogMessage(categories).validationError("%1: %2 in '%3'") << prefix << what() << getStackTrace(); + } + + QString CJsonException::stackString() + { + QStringList list; + for (const auto scope : BlackMisc::as_const(jsonStack())) + { + list.push_back(scope->m_string ? *scope->m_string : scope->m_latin1); + if (scope->m_index >= 0) { list.back() += "[" % QString::number(scope->m_index) % "]"; } + } + return list.isEmpty() ? QStringLiteral("") : list.join('.'); + } + + void CJsonScope::push() const noexcept + { + jsonStack().push_back(this); + } + + void CJsonScope::pop() const noexcept + { + Q_ASSERT(jsonStack().back() == this); + jsonStack().pop_back(); + } +} diff --git a/src/blackmisc/jsonexception.h b/src/blackmisc/jsonexception.h new file mode 100644 index 000000000..2c0129e14 --- /dev/null +++ b/src/blackmisc/jsonexception.h @@ -0,0 +1,87 @@ +/* Copyright (C) 2016 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKMISC_JSONEXCEPTION_H +#define BLACKMISC_JSONEXCEPTION_H + +#include "blackmisc/blackmiscexport.h" +#include +#include + +namespace BlackMisc +{ + class CStatusMessage; + class CLogCategoryList; + + /*! + * Thrown when a convertFromJson method encounters an unrecoverable error in JSON data. + */ + class BLACKMISC_EXPORT CJsonException : public std::runtime_error + { + public: + //! Constructor. + explicit CJsonException(const QString &message) : std::runtime_error(message.toStdString()) {} + + //! Get a stack trace of where in the JSON object tree the error occurred. + const QString &getStackTrace() const { return m_stack; } + + //! Get a status message representation. + CStatusMessage toStatusMessage(const CLogCategoryList &categories, const QString &prefix) const; + + //! Write a message to the log. + void toLogMessage(const CLogCategoryList &categories, const QString &prefix) const; + + private: + static QString stackString(); + + QString m_stack = stackString(); + }; + + /*! + * Pseudo-RAII pattern that tracks the current JSON value being converted. + * + * Will be used to construct a stack trace for CJsonException::getStackTrace. + * Class is optimized to introduce minimal overhead in the unexceptional case. + */ + class BLACKMISC_EXPORT CJsonScope + { + public: + //! Construct a scope with the given name and optional index subscript. + //! @{ + CJsonScope(const QString &name, int index = -1) noexcept : m_index(index), m_string(&name) { push(); } + CJsonScope(QLatin1String name, int index = -1) noexcept : m_index(index), m_latin1(name) { push(); } + template CJsonScope(const char (&name)[N], int index = -1) noexcept : CJsonScope(QLatin1String(name, N), index) {} + //! @} + + //! QString constructor argument must be an lvalue. + CJsonScope(const QString &&, int = -1) = delete; + + //! Not copyable. + //! @{ + CJsonScope(const CJsonScope &) = delete; + CJsonScope &operator =(const CJsonScope &) = delete; + //! @} + + //! Destructor. + ~CJsonScope() { pop(); } + + private: + void push() const noexcept; + void pop() const noexcept; + + friend class CJsonException; + int m_index = -1; + const QString *m_string = nullptr; + QLatin1String m_latin1; + }; +} + +#endif