refactor error API: add format_exc()

This commit is contained in:
Joao Paulo Magalhaes
2025-04-09 20:54:28 +01:00
parent a02ebda1f2
commit aca054d79b
4 changed files with 110 additions and 14 deletions

View File

@@ -45,6 +45,7 @@
- `err_basic_format()`: format/print a full error message for a basic error
- `location_format()`: format/print a location
- `location_format_with_context()`: useful to create a rich error message showing the YAML region causing the error, maybe even for a visit error if the source is kept and locations are enabled.
- `format_exc()`: when exceptions are enabled
- See the new header `c4/yml/error.hpp` (and `c4/yml/error.def.hpp` for definitions of the functions in `c4/yml/error.hpp`)
- See the relevant sample functions in the quickstart sample: `sample_error_basic`, `sample_error_parse` and `sample_error_visit`.
- There are breaking user-facing changes in the `Callbacks` structure:

View File

@@ -274,20 +274,17 @@ typedef enum Encoding_ {
//-----------------------------------------------------------------------------
/** holds a source or yaml file position; See also @ref
* location_format() and @ref location_format_with_context().
/** holds a source or yaml file position, for example when an error is
* detected; See also @ref location_format() and @ref
* location_format_with_context().
*
* @ingroup doc_error_handling */
struct RYML_EXPORT Location
{
//! number of bytes from the beginning of the source buffer
size_t offset;
//! line
size_t line;
//! column
size_t col;
//! file name
csubstr name;
size_t offset; ///< number of bytes from the beginning of the source buffer
size_t line; ///< line
size_t col; ///< column
csubstr name; ///< name of the file
operator bool () const noexcept { return !name.empty() || line != npos || offset != npos || col != npos; }

View File

@@ -25,7 +25,7 @@
/// @endcond
#ifdef _RYML_WITH_EXCEPTIONS
#include <stdexcept>
#include <exception>
#endif
@@ -446,7 +446,8 @@ C4_NORETURN C4_NO_INLINE void err_visit(ErrorDataVisit const& errdata, const cha
#if defined(_RYML_WITH_EXCEPTIONS) || defined(__DOXYGEN__)
/** Exception thrown by the default basic error implementation. To
* obtain the full error message, use @ref err_basic_format().
* obtain the full error message, use @ref err_basic_format(), or the
* helper @ref format_exc().
*
* @note Available only if @ref
* RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS is defined, and @ref
@@ -462,7 +463,7 @@ struct RYML_EXPORT ExceptionBasic : public std::exception
/** Exception thrown by the default parse error implementation. To
* obtain the full error message containing context, use @ref
* err_parse_format().
* err_parse_format(), or the helper @ref format_exc().
*
* @note This exception derives from @ref ExceptionBasic and can be
* catched using either type.
@@ -479,7 +480,7 @@ struct RYML_EXPORT ExceptionParse : public ExceptionBasic
/** Exception thrown by the default visit error implementation. To
* obtain the full error message containing context, use @ref
* err_visit_format().
* err_visit_format(), or the helper @ref format_exc().
*
* @note This exception derives from @ref ExceptionBasic and can be
* catched using either type.
@@ -493,6 +494,60 @@ struct RYML_EXPORT ExceptionVisit : public ExceptionBasic
ErrorDataVisit errdata_visit;
};
/** Format a basic exception to an existing char container
*
* @note Available only if @ref
* RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS is defined, and @ref
* RYML_NO_DEFAULT_CALLBACKS is NOT defined. */
template<class CharContainer>
void format_exc(CharContainer *out, ExceptionBasic const& exc)
{
out->clear();
err_basic_format([out](csubstr s){
out->append(s.str, s.len);
}, csubstr{exc.msg, strlen(exc.msg)}, exc.errdata_basic);
}
/** Format a parse exception to an existing char container
*
* @note Available only if @ref
* RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS is defined, and @ref
* RYML_NO_DEFAULT_CALLBACKS is NOT defined. */
template<class CharContainer>
void format_exc(CharContainer *out, ExceptionParse const& exc)
{
out->clear();
err_parse_format([out](csubstr s){
out->append(s.str, s.len);
}, csubstr{exc.msg, strlen(exc.msg)}, exc.errdata_parse);
}
/** Format a visit exception to an existing char container
*
* @note Available only if @ref
* RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS is defined, and @ref
* RYML_NO_DEFAULT_CALLBACKS is NOT defined. */
template<class CharContainer>
void format_exc(CharContainer *out, ExceptionVisit const& exc)
{
out->clear();
err_visit_format([out](csubstr s){
out->append(s.str, s.len);
}, csubstr{exc.msg, strlen(exc.msg)}, exc.errdata_visit);
}
/** Format a parse exception, and return a newly-created char
* container
*
* @note Available only if @ref
* RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS is defined, and @ref
* RYML_NO_DEFAULT_CALLBACKS is NOT defined. */
template<class CharContainer, class ExceptionT>
CharContainer format_exc(ExceptionT const& exc)
{
CharContainer str;
format_exc(&str, exc);
return str;
}
#endif // _RYML_WITH_EXCEPTIONS
/** @} */

View File

@@ -653,6 +653,7 @@ void test_error_basic(Location const& cpploc, const char* errmsg, Args const& ..
EXPECT_NE(get_callbacks().m_error_parse, &test_error_parse_impl);
set_callbacks(mk_test_callbacks());
std::string exc_msg;(void)exc_msg;
std::string exc_msg_full;(void)exc_msg_full;
RYML_IF_EXC(bool exc_ok = false);
C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
{
@@ -661,12 +662,14 @@ void test_error_basic(Location const& cpploc, const char* errmsg, Args const& ..
RYML_IF_EXC(catch(ExceptionBasic const& exc){
exc_msg = exc.what();
exc_ok = true;
exc_msg_full = format_exc<std::string>(exc);
})
C4_IF_EXCEPTIONS_(catch(std::exception const& exc) { exc_msg = exc.what(); }, else {;})
testloc(stored_cpploc, cpploc);
test_full_msg_args(cpploc, to_csubstr(stored_msg_full), expected);
RYML_IF_EXC(EXPECT_TRUE(exc_ok));
RYML_IF_EXC(EXPECT_EQ(exc_msg, expected));
RYML_IF_EXC(EXPECT_EQ(exc_msg_full, stored_msg_full));
reset_callbacks();
EXPECT_NE(get_callbacks().m_error_parse, &test_error_parse_impl);
}
@@ -677,6 +680,7 @@ void test_error_basic(Location const& cpploc, const char* errmsg, Args const& ..
EXPECT_NE(get_callbacks().m_error_parse, &test_error_parse_impl);
set_callbacks(mk_test_callbacks());
std::string exc_msg;(void)exc_msg;
std::string exc_msg_full;(void)exc_msg_full;
RYML_IF_EXC(bool exc_ok = false);
C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
{
@@ -685,12 +689,14 @@ void test_error_basic(Location const& cpploc, const char* errmsg, Args const& ..
RYML_IF_EXC(catch(ExceptionBasic const& exc){
exc_msg = exc.what();
exc_ok = true;
exc_msg_full = format_exc<std::string>(exc);
})
C4_IF_EXCEPTIONS_(catch(std::exception const& exc) { exc_msg = exc.what(); }, else {;})
testloc(stored_cpploc, cpploc);
test_full_msg_args(cpploc, to_csubstr(stored_msg_full), expected);
RYML_IF_EXC(EXPECT_TRUE(exc_ok));
RYML_IF_EXC(EXPECT_EQ(exc_msg, expected));
RYML_IF_EXC(EXPECT_EQ(exc_msg_full, stored_msg_full));
reset_callbacks();
EXPECT_NE(get_callbacks().m_error_parse, &test_error_parse_impl);
}
@@ -771,12 +777,14 @@ void test_check_basic(Fn &&fn, csubstr expected_msg)
set_callbacks(mk_test_callbacks());
{
std::string exc_msg;(void)exc_msg;
std::string exc_msg_full;(void)exc_msg_full;
RYML_IF_EXC(bool exc_ok = false);
C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
{
std::forward<Fn>(fn)();
}
RYML_IF_EXC(catch(ExceptionBasic const& exc){
exc_msg_full = format_exc<std::string>(exc);
exc_msg = exc.what();
exc_ok = true;
})
@@ -784,6 +792,7 @@ void test_check_basic(Fn &&fn, csubstr expected_msg)
std::cout << exc_msg << "\n";
std::cout << stored_msg_full << "\n";
RYML_IF_EXC(EXPECT_TRUE(exc_ok));
RYML_IF_EXC(EXPECT_EQ(exc_msg_full, stored_msg_full));
EXPECT_NE(csubstr::npos, to_csubstr(exc_msg).find(expected_msg));
}
reset_callbacks();
@@ -903,12 +912,14 @@ void test_error_parse(Location const& cpploc, Location const& ymlloc, const char
EXPECT_NE(get_callbacks().m_error_parse, &test_error_parse_impl);
set_callbacks(mk_test_callbacks());
std::string exc_msg;(void)exc_msg;
std::string exc_msg_full;(void)exc_msg_full;
RYML_IF_EXC(bool exc_ok = false);
C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
{
c4::yml::err_parse({cpploc, ymlloc}, errmsg, args...);
}
RYML_IF_EXC(catch(ExceptionParse const& exc){
exc_msg_full = format_exc<std::string>(exc);
exc_msg = exc.what();
exc_ok = true;
})
@@ -918,6 +929,7 @@ void test_error_parse(Location const& cpploc, Location const& ymlloc, const char
test_full_msg_args(ymlloc, to_csubstr(stored_msg_full), expected_fmt);
RYML_IF_EXC(EXPECT_TRUE(exc_ok));
RYML_IF_EXC(EXPECT_EQ(exc_msg, expected_fmt));
RYML_IF_EXC(EXPECT_EQ(exc_msg_full, stored_msg_full));
reset_callbacks();
EXPECT_NE(get_callbacks().m_error_parse, &test_error_parse_impl);
}
@@ -927,12 +939,14 @@ void test_error_parse(Location const& cpploc, Location const& ymlloc, const char
EXPECT_NE(get_callbacks().m_error_parse, &test_error_parse_impl);
set_callbacks(mk_test_callbacks());
std::string exc_msg;(void)exc_msg;
std::string exc_msg_full;(void)exc_msg_full;
RYML_IF_EXC(bool exc_ok = false);
C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
{
c4::yml::err_parse({cpploc, ymlloc}, errmsg);
}
RYML_IF_EXC(catch(ExceptionParse const& exc){
exc_msg_full = format_exc<std::string>(exc);
exc_msg = exc.what();
exc_ok = true;
})
@@ -942,6 +956,7 @@ void test_error_parse(Location const& cpploc, Location const& ymlloc, const char
test_full_msg_args(ymlloc, to_csubstr(stored_msg_full), expected);
RYML_IF_EXC(EXPECT_TRUE(exc_ok));
RYML_IF_EXC(EXPECT_EQ(exc_msg, expected));
RYML_IF_EXC(EXPECT_EQ(exc_msg_full, stored_msg_full));
reset_callbacks();
EXPECT_NE(get_callbacks().m_error_parse, &test_error_parse_impl);
}
@@ -953,6 +968,7 @@ void test_error_parse(Location const& cpploc, Location const& ymlloc, const char
cb2.m_error_parse = nullptr;
set_callbacks(cb2);
std::string exc_msg;(void)exc_msg;
std::string exc_msg_full;(void)exc_msg_full;
RYML_IF_EXC(bool exc_ok = false);
C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
{
@@ -962,6 +978,7 @@ void test_error_parse(Location const& cpploc, Location const& ymlloc, const char
exc_ok = false;
}
catch(ExceptionBasic const& exc){
exc_msg_full = format_exc<std::string>(exc);
exc_msg = exc.what();
exc_ok = true;
})
@@ -970,6 +987,7 @@ void test_error_parse(Location const& cpploc, Location const& ymlloc, const char
EXPECT_EQ(to_csubstr(stored_msg), expected);
RYML_IF_EXC(EXPECT_TRUE(exc_ok));
RYML_IF_EXC(EXPECT_EQ(exc_msg, expected));
RYML_IF_EXC(EXPECT_EQ(exc_msg_full, stored_msg_full));
reset_callbacks();
EXPECT_NE(get_callbacks().m_error_parse, &test_error_parse_impl);
}
@@ -978,6 +996,7 @@ void test_error_parse(Location const& cpploc, Location const& ymlloc, const char
SCOPED_TRACE("custom handler, parse falls back to basic");
Callbacks cb2 = mk_test_callbacks();
std::string exc_msg;(void)exc_msg;
std::string exc_msg_full;(void)exc_msg_full;
RYML_IF_EXC(bool exc_ok = false);
EXPECT_NE(get_callbacks().m_error_parse, &test_error_parse_impl);
cb2.m_error_parse = nullptr;
@@ -990,6 +1009,7 @@ void test_error_parse(Location const& cpploc, Location const& ymlloc, const char
exc_ok = false;
}
catch(ExceptionBasic const& exc){
exc_msg_full = format_exc<std::string>(exc);
exc_msg = exc.what();
exc_ok = true;
})
@@ -998,6 +1018,7 @@ void test_error_parse(Location const& cpploc, Location const& ymlloc, const char
EXPECT_EQ(to_csubstr(stored_msg), expected_fmt);
RYML_IF_EXC(EXPECT_TRUE(exc_ok));
RYML_IF_EXC(EXPECT_EQ(exc_msg, expected_fmt));
RYML_IF_EXC(EXPECT_EQ(exc_msg_full, stored_msg_full));
reset_callbacks();
EXPECT_NE(get_callbacks().m_error_parse, &test_error_parse_impl);
}
@@ -1031,6 +1052,7 @@ void test_error_parse(Location const& cpploc, Location const& ymlloc, const char
SCOPED_TRACE("default handler exceptions, no format");
bool exc_ok = false;
std::string exc_msg;
std::string exc_msg_full;
try
{
c4::yml::err_parse({cpploc, ymlloc}, errmsg);
@@ -1039,6 +1061,7 @@ void test_error_parse(Location const& cpploc, Location const& ymlloc, const char
{
exc_ok = true;
exc_msg = exc.what();
exc_msg_full = format_exc<std::string>(exc);
testloc(exc.errdata_parse.ymlloc, ymlloc);
testloc(exc.errdata_parse.cpploc, cpploc);
}
@@ -1054,6 +1077,7 @@ void test_error_parse(Location const& cpploc, Location const& ymlloc, const char
SCOPED_TRACE("parse err can be caught as basic");
bool exc_ok = false;
std::string exc_msg;
std::string exc_msg_full;
try
{
c4::yml::err_parse({cpploc, ymlloc}, errmsg, args...);
@@ -1062,6 +1086,7 @@ void test_error_parse(Location const& cpploc, Location const& ymlloc, const char
{
exc_ok = true;
exc_msg = exc.what();
exc_msg_full = format_exc<std::string>(exc);
testloc(exc.errdata_basic.location, ymlloc);
}
catch(std::exception const& exc)
@@ -1108,18 +1133,21 @@ void test_check_parse(Fn &&fn, csubstr expected_msg)
set_callbacks(mk_test_callbacks());
{
std::string exc_msg;(void)exc_msg;
std::string exc_msg_full;(void)exc_msg_full;
RYML_IF_EXC(bool exc_ok = false);
C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
{
std::forward<Fn>(fn)();
}
RYML_IF_EXC(catch(ExceptionParse const& exc){
exc_msg_full = format_exc<std::string>(exc);
exc_msg = exc.what();
exc_ok = true;
})
C4_IF_EXCEPTIONS_(catch(std::exception const& exc) { exc_msg = exc.what(); }, else { exc_msg = stored_msg; })
std::cout << stored_msg_full << "\n";
RYML_IF_EXC(EXPECT_TRUE(exc_ok));
RYML_IF_EXC(EXPECT_EQ(exc_msg_full, stored_msg_full));
EXPECT_NE(csubstr::npos, to_csubstr(exc_msg).find(expected_msg));
}
reset_callbacks();
@@ -1188,12 +1216,14 @@ void test_error_visit(Location const& cpploc, Tree const* t, id_type id, const c
EXPECT_NE(get_callbacks().m_error_visit, &test_error_visit_impl);
set_callbacks(mk_test_callbacks());
std::string exc_msg;(void)exc_msg;
std::string exc_msg_full;(void)exc_msg_full;
RYML_IF_EXC(bool exc_ok = false);
C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
{
c4::yml::err_visit({cpploc, t, id}, errmsg, args...);
}
RYML_IF_EXC(catch(ExceptionVisit const& exc){
exc_msg_full = format_exc<std::string>(exc);
exc_msg = exc.what();
exc_ok = true;
})
@@ -1204,6 +1234,7 @@ void test_error_visit(Location const& cpploc, Tree const* t, id_type id, const c
EXPECT_EQ(stored_id, id);
RYML_IF_EXC(EXPECT_TRUE(exc_ok));
RYML_IF_EXC(EXPECT_EQ(exc_msg, expected_fmt));
RYML_IF_EXC(EXPECT_EQ(exc_msg_full, stored_msg_full));
reset_callbacks();
EXPECT_NE(get_callbacks().m_error_visit, &test_error_visit_impl);
}
@@ -1214,12 +1245,14 @@ void test_error_visit(Location const& cpploc, Tree const* t, id_type id, const c
EXPECT_NE(get_callbacks().m_error_visit, &test_error_visit_impl);
set_callbacks(mk_test_callbacks());
std::string exc_msg;(void)exc_msg;
std::string exc_msg_full;(void)exc_msg_full;
RYML_IF_EXC(bool exc_ok = false);
C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
{
c4::yml::err_visit({cpploc, t, id}, errmsg);
}
RYML_IF_EXC(catch(ExceptionVisit const& exc){
exc_msg_full = format_exc<std::string>(exc);
exc_msg = exc.what();
exc_ok = true;
})
@@ -1230,6 +1263,7 @@ void test_error_visit(Location const& cpploc, Tree const* t, id_type id, const c
EXPECT_EQ(stored_id, id);
RYML_IF_EXC(EXPECT_TRUE(exc_ok));
RYML_IF_EXC(EXPECT_EQ(exc_msg, expected));
RYML_IF_EXC(EXPECT_EQ(exc_msg_full, stored_msg_full));
reset_callbacks();
EXPECT_NE(get_callbacks().m_error_visit, &test_error_visit_impl);
}
@@ -1243,6 +1277,7 @@ void test_error_visit(Location const& cpploc, Tree const* t, id_type id, const c
set_callbacks(cb);
EXPECT_EQ(get_callbacks().m_error_visit, nullptr);
std::string exc_msg;(void)exc_msg;
std::string exc_msg_full;(void)exc_msg_full;
RYML_IF_EXC(bool exc_ok = false);
C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
{
@@ -1252,6 +1287,7 @@ void test_error_visit(Location const& cpploc, Tree const* t, id_type id, const c
exc_ok = false;
}
catch(ExceptionBasic const& exc){
exc_msg_full = format_exc<std::string>(exc);
exc_msg = exc.what();
exc_ok = true;
})
@@ -1262,6 +1298,7 @@ void test_error_visit(Location const& cpploc, Tree const* t, id_type id, const c
EXPECT_EQ(stored_id, NONE);
RYML_IF_EXC(EXPECT_TRUE(exc_ok));
RYML_IF_EXC(EXPECT_EQ(exc_msg, expected_fmt));
RYML_IF_EXC(EXPECT_EQ(exc_msg_full, stored_msg_full));
reset_callbacks();
EXPECT_NE(get_callbacks().m_error_visit, &test_error_visit_impl);
}
@@ -1275,6 +1312,7 @@ void test_error_visit(Location const& cpploc, Tree const* t, id_type id, const c
set_callbacks(cb);
EXPECT_EQ(get_callbacks().m_error_visit, nullptr);
std::string exc_msg;(void)exc_msg;
std::string exc_msg_full;(void)exc_msg_full;
RYML_IF_EXC(bool exc_ok = false);
C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
{
@@ -1284,6 +1322,7 @@ void test_error_visit(Location const& cpploc, Tree const* t, id_type id, const c
exc_ok = false;
}
catch(ExceptionBasic const& exc){
exc_msg_full = format_exc<std::string>(exc);
exc_msg = exc.what();
exc_ok = true;
})
@@ -1294,6 +1333,7 @@ void test_error_visit(Location const& cpploc, Tree const* t, id_type id, const c
EXPECT_EQ(stored_id, NONE);
RYML_IF_EXC(EXPECT_TRUE(exc_ok));
RYML_IF_EXC(EXPECT_EQ(exc_msg, expected));
RYML_IF_EXC(EXPECT_EQ(exc_msg_full, stored_msg_full));
reset_callbacks();
EXPECT_NE(get_callbacks().m_error_visit, &test_error_visit_impl);
}
@@ -1408,18 +1448,21 @@ void test_check_visit(Fn &&fn, csubstr expected_msg)
set_callbacks(mk_test_callbacks());
{
std::string exc_msg;(void)exc_msg;
std::string exc_msg_full;(void)exc_msg_full;
RYML_IF_EXC(bool exc_ok = false);
C4_IF_EXCEPTIONS_(try, if(setjmp(s_jmp_env_expect_error) == 0))
{
std::forward<Fn>(fn)();
}
RYML_IF_EXC(catch(ExceptionVisit const& exc){
exc_msg_full = format_exc<std::string>(exc);
exc_msg = exc.what();
exc_ok = true;
})
C4_IF_EXCEPTIONS_(catch(std::exception const& exc) { exc_msg = exc.what(); }, else { exc_msg = stored_msg; })
std::cout << stored_msg_full << "\n";
RYML_IF_EXC(EXPECT_TRUE(exc_ok));
RYML_IF_EXC(EXPECT_EQ(exc_msg_full, stored_msg_full));
EXPECT_NE(csubstr::npos, to_csubstr(exc_msg).find(expected_msg));
}
reset_callbacks();