From cbdf3d2f3a5edcaeab58ff81cb1261e3a9277583 Mon Sep 17 00:00:00 2001 From: Emil Dotchevski Date: Fri, 23 Jan 2026 19:20:47 -0500 Subject: [PATCH] Refactored serialization api, added encoder for boost json --- doc/exception.adoc | 446 ++++++++++++++++-- .../detail/{writer.hpp => encoder.hpp} | 46 +- .../exception/detail/error_info_impl.hpp | 6 +- .../exception/diagnostic_information.hpp | 30 +- include/boost/exception/info.hpp | 39 +- .../serialization/boost_json_encoder.hpp | 64 +++ ...n_writer.hpp => nlohmann_json_encoder.hpp} | 16 +- test/Jamfile.v2 | 48 +- test/boost_json_test.cpp | 148 ++++++ ...ohmann_test.cpp => nlohmann_json_test.cpp} | 78 +-- 10 files changed, 741 insertions(+), 180 deletions(-) rename include/boost/exception/detail/{writer.hpp => encoder.hpp} (63%) create mode 100644 include/boost/exception/serialization/boost_json_encoder.hpp rename include/boost/exception/serialization/{nlohmann_writer.hpp => nlohmann_json_encoder.hpp} (56%) create mode 100644 test/boost_json_test.cpp rename test/{nlohmann_test.cpp => nlohmann_json_test.cpp} (55%) diff --git a/doc/exception.adoc b/doc/exception.adoc index 08e26ba..900bb1f 100644 --- a/doc/exception.adoc +++ b/doc/exception.adoc @@ -453,6 +453,164 @@ std::exception::what: example_io error [struct boost::errinfo_file_open_mode_ *] = rb ---- +[[tutorial_serialization]] +=== Serialization + +Boost Exception provides a serialization API that enables exporting diagnostic information into different formats, such as JSON. This is useful for structured logging, remote debugging, or integrating with monitoring systems. + +To serialize diagnostic information, use the following functions: + +* `<>` -- serializes diagnostic information for a given exception. +* `<>` -- serializes diagnostic information for the current exception. + +The serialization system uses `output` and `output_at` functions found via ADL: + +* `output(enc, x)` serializes value `x` directly to encoder `enc`. +* `output_at(enc, x, name)` serializes value `x` to encoder `enc` as a named field. + +[[custom-encoders]] +==== Custom Encoders + +To support exporting to a specific format, users define an encoder class with associated `output` and `output_at` function templates: + +[source,c++] +---- +struct my_encoder +{ + template + friend void output(my_encoder & enc, T const & x) + { + // output value x to enc + } + + template + friend void output_at(my_encoder & enc, T const & x, char const * name) + { + // output x to enc as a named field + } +}; +---- + +The `output_at` function typically creates a nested scope (e.g. a JSON object) and then calls `output` to serialize the value. + +To enable serialization to a custom encoder type, define a `<>` function template in the `boost::exception_serialization` namespace: + +[source,c++] +---- +namespace boost { namespace exception_serialization { + +template +void serialize(Handle & h, T const & x, char const * name) +{ + h.dispatch([&](my_encoder & enc) { + output_at(enc, x, name); + }); +} + +} } +---- + +The `<>` function template takes a handle reference `h` (of unspecified type) that holds an encoder, the object to be serialized, and its type name. Call `h.dispatch` with a single-argument function F to detect the encoder type based on F's argument type; F is called only if the handle contains an encoder of that type. + +To support multiple output formats, pass multiple functions to `h.dispatch`: + +[source,c++] +---- +h.dispatch( + [&](json_encoder & enc) { output_at(enc, x, name); }, + [&](xml_encoder & enc) { output_at(enc, x, name); } +); +---- + +==== JSON Serialization + +Boost Exception provides two JSON encoders: + +* `<>` for https://www.boost.org/doc/libs/release/libs/json/[Boost.JSON] +* `<>` based on ADL calls to `to_json` (compatible with https://github.com/nlohmann/json[nlohmann/json]) + +Below is an example using `nlohmann_json_encoder` with nlohmann/json. We just need to define the required `<>` function template (see <>): + +[source,c++] +---- +#include +#include "nlohmann/json.hpp" + +using nlohmann_json_encoder = boost::exception_serialization::nlohmann_json_encoder; + +namespace boost { namespace exception_serialization { + +template +void serialize(Handle & h, T const & x, char const * name) +{ + h.dispatch([&](nlohmann_json_encoder & enc) { + output_at(enc, x, name); + }); +} + +} } +---- + +With this in place, we can output diagnostic information to JSON: + +[source,c++] +---- +#include +#include +#include + +struct api_response +{ + int status; + std::string message; + + template + friend void to_json(Json & j, api_response const & e) + { + j["status"] = e.status; + j["message"] = e.message; + } +}; + +typedef boost::error_info errinfo_api_response; +typedef boost::error_info errinfo_request_url; + +struct api_error : virtual boost::exception, virtual std::exception { }; + +.... + +nlohmann::json j; +try +{ + BOOST_THROW_EXCEPTION(api_error() + << errinfo_api_response({403, "Access denied"}) + << errinfo_request_url("/api/admin/settings")); +} +catch(boost::exception & e) +{ + nlohmann_json_encoder enc{j}; + boost::serialize_diagnostic_information_to(enc, e); +} +std::cout << j.dump(2) << std::endl; +---- + +.Output: +[source,json] +---- +{ + "errinfo_api_response_": { + "status": 403, + "message": "Access denied" + }, + "errinfo_request_url_": "/api/admin/settings" +} +---- + +[.text-right] +<> | <> + +NOTE: In the example above, `api_response` uses an unqualified call to `to_json` for serialization. This is to demonstrate that `nlohmann_json_encoder` handles third party types with suitable `to_json` overloads automatically. If instead we defined a function `output` compatible with the Boost Exception serialization API, it would make `api_response` compatible with any Boost Exception encoder. + [[synopsis]] == Synopsis @@ -604,11 +762,17 @@ namespace boost char const * diagnostic_information_what( boost::exception const & e, bool verbose=true ) throw(); std::string current_exception_diagnostic_information(); + + template + void serialize_diagnostic_information_to( Encoder & enc, E const & e ); + + template + void serialize_current_exception_diagnostic_information_to( Encoder & enc ); } ---- [.text-right] -Reference: <> | <> | <> +Reference: <> | <> | <> | <> | <> ==== [[synopsis-current_exception_cast]] @@ -904,6 +1068,67 @@ Reference: <> This header includes all Boost Exception headers except `<>` (unless BOOST_NO_EXCEPTIONS is defined.) ==== +[[synopsis-serialization]] +=== Serialization + +[[synopsis-boost_json_encoder]] +==== `boost_json_encoder.hpp` + +==== +.#include +[source,c++] +---- +namespace boost { namespace exception_serialization { + +struct boost_json_encoder +{ + boost::json::value & v_; + + // Enabled if x is assignable to boost::json::value, or + // if tag_invoke is defined for boost::json::value_from_tag. + template + friend void output( boost_json_encoder &, T const & x ); + + template + friend void output_at( boost_json_encoder &, T const &, char const * name ); +}; + +} } +---- + +[.text-right] +Reference: <> +==== + +[[synopsis-nlohmann_json_encoder]] +==== `nlohmann_json_encoder.hpp` + +==== +.#include +[source,c++] +---- +namespace boost { namespace exception_serialization { + +template +struct nlohmann_json_encoder +{ + Json & j_; + + // Enabled if to_json is available for Json and T. + template + friend void output( nlohmann_json_encoder &, T const & x ); + + template + friend void output_at( nlohmann_json_encoder &, T const &, char const * name ); +}; + +} } +---- + +[.text-right] +Reference: <> +==== + [[reference]] == Reference @@ -919,60 +1144,36 @@ endif::[] [[types]] === Types -[[exception]] -==== `exception` +[[boost_json_encoder]] +==== `boost_json_encoder` -.#include +.#include [source,c++] ---- -namespace boost +namespace boost { namespace exception_serialization { + +struct boost_json_encoder { - class exception - { - protected: + boost::json::value & v_; - exception(); - exception( exception const & x ); - ~exception(); - }; -} + // Enabled if x is assignable to boost::json::value, or + // if tag_invoke is defined for boost::json::value_from_tag. + template + friend void output( boost_json_encoder &, T const & x ); + + template + friend void output_at( boost_json_encoder &, T const &, char const * name ); +}; + +} } ---- -Class `boost::exception` is designed to be used as a universal base for user-defined exception types. +The `boost_json_encoder` type serializes objects to JSON format using https://www.boost.org/doc/libs/release/libs/json/[Boost.JSON]. The `output` function is enabled for: -An object of any type deriving from `boost::exception` can store data of arbitrary types, using the `<>` wrapper and `<>`. +* Types directly assignable to `boost::json::value` +* Types for which a `tag_invoke` overload for `value_from_tag` can be found via ADL -To retrieve data from a `boost::exception` object, use the `<>` function template. - -[[exception_constructors]] -===== `exception::exception` - -[source,c++] ----- -exception(); -exception( exception const & x ); ----- - -Effects: :: -+ --- -* Default constructor: initializes an empty `boost::exception` object. -* Copy constructor: initializes a `boost::exception` object which shares with x the pointers to all currently stored data. Subsequently, data can be added to or retrieved from both exception objects interchangeably, however doing so concurrently from multiple threads is undefined behavior. --- - -Throws: :: Nothing. - -[[exception_destructor]] -===== `exception::~exception` - -[source,c++] ----- -~exception(); ----- - -Effects: :: Releases all resources associated with the `boost::exception` object. - -Throws: :: Nothing. +See <>. ''' @@ -1288,6 +1489,63 @@ This type is designed to be used as a standard `<>` insta ''' +[[exception]] +==== `exception` + +.#include +[source,c++] +---- +namespace boost +{ + class exception + { + protected: + + exception(); + exception( exception const & x ); + ~exception(); + }; +} +---- + +Class `boost::exception` is designed to be used as a universal base for user-defined exception types. + +An object of any type deriving from `boost::exception` can store data of arbitrary types, using the `<>` wrapper and `<>`. + +To retrieve data from a `boost::exception` object, use the `<>` function template. + +[[exception_constructors]] +===== `exception::exception` + +[source,c++] +---- +exception(); +exception( exception const & x ); +---- + +Effects: :: ++ +-- +* Default constructor: initializes an empty `boost::exception` object. +* Copy constructor: initializes a `boost::exception` object which shares with x the pointers to all currently stored data. Subsequently, data can be added to or retrieved from both exception objects interchangeably, however doing so concurrently from multiple threads is undefined behavior. +-- + +Throws: :: Nothing. + +[[exception_destructor]] +===== `exception::~exception` + +[source,c++] +---- +~exception(); +---- + +Effects: :: Releases all resources associated with the `boost::exception` object. + +Throws: :: Nothing. + +''' + [[exception_ptr]] ==== `exception_ptr` @@ -1321,6 +1579,34 @@ Nesting of exceptions: :: An `exception_ptr` can be added as `< +[source,c++] +---- +namespace boost { namespace exception_serialization { + +template +struct nlohmann_json_encoder +{ + Json & j_; + + // Enabled if to_json is available for Json and T. + template + friend void output( nlohmann_json_encoder &, T const & x ); + + template + friend void output_at( nlohmann_json_encoder &, T const &, char const * name ); +}; + +} } +---- + +The `nlohmann_json_encoder` type serializes objects to JSON format based on ADL calls to `to_json`. This is compatible with https://github.com/nlohmann/json[nlohmann/json]. See <>. + +''' + [[original_exception_type]] ==== `original_exception_type` @@ -1720,6 +2006,74 @@ Throws: :: The exception to which ep refers. ''' +[[serialize]] +==== `serialize` + +[source,c++] +---- +namespace boost { namespace exception_serialization { + +template +void serialize( Handle & h, E const & x, char const * name ); + +} } +---- + +The `serialize` function template is a user-defined customization point. If provided, it is called by the serialization system to output objects to an encoder; see <>. + +''' + +[[serialize_current_exception_diagnostic_information_to]] +==== `serialize_current_exception_diagnostic_information_to` + +.#include +[source,c++] +---- +namespace boost +{ + template + void serialize_current_exception_diagnostic_information_to( Encoder & enc ); +} +---- + +Requirements: :: This function must not be called outside of a catch block. + +Effects: :: Serializes diagnostic information for the current exception to the encoder `enc`. Equivalent to calling `<>` with the current exception object. + +See <>. + +''' + +[[serialize_diagnostic_information_to]] +==== `serialize_diagnostic_information_to` + +.#include +[source,c++] +---- +namespace boost +{ + template + void serialize_diagnostic_information_to( Encoder & enc, E const & e ); +} +---- + +Serializes diagnostic information for the exception `e` to the encoder `enc`. + +Requirements: :: E must be convertible to either `boost::<> const &` or `std::exception const &`. + +Effects: :: Outputs diagnostic information about the exception to the encoder, including: ++ +-- +* Throw location (file, line, function) if available +* Dynamic exception type (if RTTI is enabled) +* `std::exception::what()` if available +* All `<>` objects stored in the exception +-- + +See <>. + +''' + [[throw_exception]] ==== `throw_exception` diff --git a/include/boost/exception/detail/writer.hpp b/include/boost/exception/detail/encoder.hpp similarity index 63% rename from include/boost/exception/detail/writer.hpp rename to include/boost/exception/detail/encoder.hpp index d6f7893..92beefa 100644 --- a/include/boost/exception/detail/writer.hpp +++ b/include/boost/exception/detail/encoder.hpp @@ -3,8 +3,8 @@ //Distributed under the Boost Software License, Version 1.0. (See accompanying //file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -#ifndef BOOST_EXCEPTION_DETAIL_WRITER_HPP_INCLUDED -#define BOOST_EXCEPTION_DETAIL_WRITER_HPP_INCLUDED +#ifndef BOOST_EXCEPTION_DETAIL_ENCODER_HPP_INCLUDED +#define BOOST_EXCEPTION_DETAIL_ENCODER_HPP_INCLUDED #include #include @@ -18,7 +18,7 @@ boost namespace exception_serialization { - struct writer_adl {}; + struct encoder_adl {}; } namespace @@ -42,14 +42,14 @@ boost }; class - writer: - exception_serialization::writer_adl + encoder: + exception_serialization::encoder_adl { - writer(writer const &) = delete; - writer & operator=(writer const &) = delete; + encoder(encoder const &) = delete; + encoder & operator=(encoder const &) = delete; core::typeinfo const * type_; - void * w_; + void * e_; bool dispatch_() @@ -61,10 +61,10 @@ boost bool dispatch_(F1 && f1, Fn && ... fn) { - using writer_type = typename std::decay::type::operator())>::type>::type; - if (writer_type * w = get()) + using encoder_type = typename std::decay::type::operator())>::type>::type; + if (encoder_type * e = get()) { - std::forward(f1)(*w); + std::forward(f1)(*e); return true; } return dispatch_(std::forward(fn)...); @@ -72,21 +72,21 @@ boost protected: - template + template explicit - writer(Writer * w) noexcept: - type_(&BOOST_CORE_TYPEID(Writer)), - w_(w) + encoder(Encoder * e) noexcept: + type_(&BOOST_CORE_TYPEID(Encoder)), + e_(e) { } public: - template - Writer * + template + Encoder * get() noexcept { - return *type_ == BOOST_CORE_TYPEID(Writer) ? static_cast(w_) : nullptr; + return *type_ == BOOST_CORE_TYPEID(Encoder) ? static_cast(e_) : nullptr; } template @@ -97,14 +97,14 @@ boost } }; - template + template struct - writer_adaptor: - writer + encoder_adaptor: + encoder { explicit - writer_adaptor(Writer & w) noexcept: - writer(&w) + encoder_adaptor(Encoder & e) noexcept: + encoder(&e) { } }; diff --git a/include/boost/exception/detail/error_info_impl.hpp b/include/boost/exception/detail/error_info_impl.hpp index 176aed0..3c37d2c 100644 --- a/include/boost/exception/detail/error_info_impl.hpp +++ b/include/boost/exception/detail/error_info_impl.hpp @@ -13,7 +13,7 @@ #include #include -namespace boost { namespace exception_detail { class writer; } } +namespace boost { namespace exception_detail { class encoder; } } #ifndef BOOST_EXCEPTION_ENABLE_WARNINGS #if defined(__GNUC__) && __GNUC__*100+__GNUC_MINOR__>301 @@ -40,7 +40,7 @@ boost virtual std::string name_value_string() const = 0; virtual error_info_base * clone() const = 0; - virtual void write_to(writer &) const = 0; + virtual void serialize_to(encoder &) const = 0; virtual ~error_info_base() BOOST_NOEXCEPT_OR_NOTHROW @@ -100,7 +100,7 @@ boost error_info & operator=( error_info && x ); #endif std::string name_value_string() const; - void write_to(exception_detail::writer &) const; + void serialize_to(exception_detail::encoder &) const; value_type v_; }; } diff --git a/include/boost/exception/diagnostic_information.hpp b/include/boost/exception/diagnostic_information.hpp index 1465394..fc1d037 100644 --- a/include/boost/exception/diagnostic_information.hpp +++ b/include/boost/exception/diagnostic_information.hpp @@ -209,9 +209,9 @@ boost namespace exception_detail { - template + template void - write_diagnostic_information_to_impl_( boost::exception const * be, std::exception const * se, Writer & w ) + serialize_diagnostic_information_to_impl_( boost::exception const * be, std::exception const * se, Encoder & e ) { if( !be && !se ) return; @@ -224,44 +224,44 @@ boost if( be ) { if( char const * const * f=get_error_info(*be) ) - write_nested(w, *f, "throw_file"); + output_at(e, *f, "throw_file"); if( int const * l=get_error_info(*be) ) - write_nested(w, *l, "throw_line"); + output_at(e, *l, "throw_line"); if( char const * const * fn=get_error_info(*be) ) - write_nested(w, *fn, "throw_function"); + output_at(e, *fn, "throw_function"); } #ifndef BOOST_NO_RTTI if( be || se ) - write_nested(w, core::demangle((be?(BOOST_EXCEPTION_DYNAMIC_TYPEID(*be)):(BOOST_EXCEPTION_DYNAMIC_TYPEID(*se))).type_->name()).c_str(), "dynamic_exception_type"); + output_at(e, core::demangle((be?(BOOST_EXCEPTION_DYNAMIC_TYPEID(*be)):(BOOST_EXCEPTION_DYNAMIC_TYPEID(*se))).type_->name()).c_str(), "dynamic_exception_type"); #endif if( se ) if( char const * wh = se->what() ) - write_nested(w, wh, "std::exception::what"); + output_at(e, wh, "std::exception::what"); if( be ) if( error_info_container * c = be->data_.get() ) { - writer_adaptor wa(w); - c->write_to(wa); + encoder_adaptor ea(e); + c->serialize_to(ea); } } } - template + template void - write_diagnostic_information_to( T const & e, Writer & w ) + serialize_diagnostic_information_to( T const & e, Encoder & enc ) { - exception_detail::write_diagnostic_information_to_impl_(exception_detail::get_boost_exception(&e),exception_detail::get_std_exception(&e),w); + exception_detail::serialize_diagnostic_information_to_impl_(exception_detail::get_boost_exception(&e),exception_detail::get_std_exception(&e),enc); } #ifndef BOOST_NO_EXCEPTIONS - template + template void - write_current_exception_diagnostic_information_to( Writer & w ) + serialize_current_exception_diagnostic_information_to( Encoder & e ) { boost::exception const * be=current_exception_cast(); std::exception const * se=current_exception_cast(); if( be || se ) - exception_detail::write_diagnostic_information_to_impl_(be,se,w); + exception_detail::serialize_diagnostic_information_to_impl_(be,se,e); } #endif } diff --git a/include/boost/exception/info.hpp b/include/boost/exception/info.hpp index b1c8249..73e995a 100644 --- a/include/boost/exception/info.hpp +++ b/include/boost/exception/info.hpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include @@ -33,31 +33,12 @@ boost namespace exception_serialization { - template - typename std::enable_if::value>::type - serialize(Writer &, T const &, char const *, Unused && ...) + // Stub + template + typename std::enable_if::value>::type + serialize(Encoder &, T const &, char const *, Deprioritize...) { } - - template - void - write(Writer & w, error_info const & e) - { - write(w, e.value()); - } - } - - namespace - exception_detail - { - template - void - serialize_(writer & w, error_info const & x) - { - using namespace boost::exception_serialization; - char buf[256]; - serialize(w, x.value(), to_zstr(buf, get_pretty_tag_type_name())); - } } template @@ -89,9 +70,11 @@ boost inline void error_info:: - write_to(exception_detail::writer & w) const + serialize_to(exception_detail::encoder & e) const { - exception_detail::serialize_(w, *this); + char buf[256]; + using namespace exception_serialization; + serialize(e, value(), to_zstr(buf, exception_detail::get_pretty_tag_type_name())); } namespace @@ -150,12 +133,12 @@ boost } void - write_to( writer & w ) const + serialize_to( encoder & e ) const { for( error_info_map::const_iterator i=info_.begin(),end=info_.end(); i!=end; ++i ) { error_info_base const & x = *i->second; - x.write_to(w); + x.serialize_to(e); } } diff --git a/include/boost/exception/serialization/boost_json_encoder.hpp b/include/boost/exception/serialization/boost_json_encoder.hpp new file mode 100644 index 0000000..6e96808 --- /dev/null +++ b/include/boost/exception/serialization/boost_json_encoder.hpp @@ -0,0 +1,64 @@ +//Copyright (c) 2006-2026 Emil Dotchevski and Reverge Studios, Inc. + +//Distributed under the Boost Software License, Version 1.0. (See accompanying +//file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_EXCEPTION_SERIALIZATION_BOOST_JSON_ENCODER_HPP_INCLUDED +#define BOOST_EXCEPTION_SERIALIZATION_BOOST_JSON_ENCODER_HPP_INCLUDED + +#include + +namespace +boost + { + namespace + json + { + class value; + struct value_from_tag; + } + + namespace + exception_serialization + { + // Baast.JSON does not provide ADL interface for serializing user-defined types. + // This limits the functionality of boost_json_encoder to only types that provide tag_invoke. + template + struct + boost_json_encoder_ + { + Value & v_; + + template + friend + auto + output(boost_json_encoder_ & e, T const & x) -> decltype(std::declval() = x, void()) + { + e.v_ = x; + } + + template + friend + auto + output(boost_json_encoder_ & e, T const & x) -> decltype(tag_invoke(std::declval(), std::declval(), x), void()) + { + tag_invoke(ValueFromTag{}, e.v_, x); + } + + template + friend + void + output_at(boost_json_encoder_ & e, T const & x, char const * name) + { + if( e.v_.is_null() ) + e.v_.emplace_object(); + boost_json_encoder_ nested{e.v_.as_object()[name]}; + output(nested, x); + } + }; + + using boost_json_encoder = boost_json_encoder_<>; + } + } + +#endif diff --git a/include/boost/exception/serialization/nlohmann_writer.hpp b/include/boost/exception/serialization/nlohmann_json_encoder.hpp similarity index 56% rename from include/boost/exception/serialization/nlohmann_writer.hpp rename to include/boost/exception/serialization/nlohmann_json_encoder.hpp index 3861449..003cafd 100644 --- a/include/boost/exception/serialization/nlohmann_writer.hpp +++ b/include/boost/exception/serialization/nlohmann_json_encoder.hpp @@ -3,8 +3,8 @@ //Distributed under the Boost Software License, Version 1.0. (See accompanying //file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -#ifndef BOOST_EXCEPTION_SERIALIZATION_NLOHMANN_WRITER_HPP_INCLUDED -#define BOOST_EXCEPTION_SERIALIZATION_NLOHMANN_WRITER_HPP_INCLUDED +#ifndef BOOST_EXCEPTION_SERIALIZATION_NLOHMANN_JSON_ENCODER_HPP_INCLUDED +#define BOOST_EXCEPTION_SERIALIZATION_NLOHMANN_JSON_ENCODER_HPP_INCLUDED #include @@ -16,25 +16,25 @@ boost { template struct - nlohmann_writer + nlohmann_json_encoder { Json & j_; template friend auto - write(nlohmann_writer & w, T const & x) -> decltype(to_json(std::declval(), x)) + output(nlohmann_json_encoder & e, T const & x) -> decltype(to_json(std::declval(), x)) { - to_json(w.j_, x); + to_json(e.j_, x); } template friend void - write_nested(nlohmann_writer & w, T const & x, char const * name) + output_at(nlohmann_json_encoder & e, T const & x, char const * name) { - nlohmann_writer nested{w.j_[name]}; - write(nested, x); + nlohmann_json_encoder nested{e.j_[name]}; + output(nested, x); } }; } diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index bacdc83..5bc3ffe 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -11,10 +11,11 @@ project : requirements /boost/exception//boost_exception ; #to_string -run is_output_streamable_test.cpp ; run has_to_string_test.cpp ; -run to_string_test.cpp ; +run is_output_streamable_test.cpp ; run to_string_stub_test.cpp ; +run to_string_test.cpp ; + compile-fail to_string_fail.cpp ; #exception @@ -23,48 +24,42 @@ run 1-throw_exception_test.cpp : : : on ; run 2-throw_exception_no_exceptions_test.cpp : : : off ; run 3-throw_exception_no_integration_test.cpp : : : on ; run 4-throw_exception_no_both_test.cpp : : : off ; +run boost_json_test.cpp /boost/json//boost_json : : : on ; run cloning_test.cpp : : : on ; -run copy_exception_test.cpp ../../thread/src/tss_null.cpp /boost/thread//boost_thread : : : multi on ; run copy_exception_no_exceptions_test.cpp : : : off on ; -run unknown_exception_test.cpp : : : on ; -run exception_test.cpp : : : on ; +run copy_exception_test.cpp ../../thread/src/tss_null.cpp /boost/thread//boost_thread : : : multi on ; +run current_exception_cast_test.cpp : : : on ; +run diagnostic_information_test.cpp : : : on ; run enable_error_info_test.cpp helper1.cpp : : : on ; -run throw_exception_test.cpp helper2.cpp : : : on ; run errno_test.cpp : : : on ; run error_info_basic_test.cpp : : : on ; -run error_info_lv_test.cpp : : : on ; run error_info_lv_const_test.cpp : : : on ; -run error_info_rv_test.cpp : : : on ; +run error_info_lv_test.cpp : : : on ; run error_info_rv_const_test.cpp : : : on ; -run diagnostic_information_test.cpp : : : on ; -run refcount_ptr_test.cpp ; -run current_exception_cast_test.cpp : : : on ; -run no_exceptions_test.cpp : : : off ; +run error_info_rv_test.cpp : : : on ; run errinfos_test.cpp : : : on ; -run nlohmann_test.cpp : : : on ; -run exception_ptr_test.cpp/BOOST_ENABLE_NON_INTRUSIVE_EXCEPTION_PTR ../../thread/src/tss_null.cpp /boost/exception//boost_exception /boost/thread//boost_thread : : : multi on : non_intrusive_exception_ptr_test ; run exception_ptr_test.cpp ../../thread/src/tss_null.cpp /boost/thread//boost_thread : : : multi on ; +run exception_ptr_test.cpp/BOOST_ENABLE_NON_INTRUSIVE_EXCEPTION_PTR ../../thread/src/tss_null.cpp /boost/exception//boost_exception /boost/thread//boost_thread : : : multi on : non_intrusive_exception_ptr_test ; run exception_ptr_test2.cpp ; +run exception_test.cpp : : : on ; +run nlohmann_json_test.cpp : : : on ; +run no_exceptions_test.cpp : : : off ; +run refcount_ptr_test.cpp ; +run throw_exception_test.cpp helper2.cpp : : : on ; +run unknown_exception_test.cpp : : : on ; lib visibility_test_lib : visibility_test_lib.cpp : hidden on ; run visibility_test.cpp visibility_test_lib/shared : : : hidden on ; +compile-fail error_info_const_fail.cpp ; compile-fail exception_fail.cpp ; compile-fail throw_exception_fail.cpp ; -compile-fail error_info_const_fail.cpp ; #headers -compile exception_ptr_hpp_test.cpp ; -compile diagnostic_information_hpp_test.cpp ; -compile error_info_hpp_test.cpp ; -compile get_error_info_hpp_test.cpp ; -compile info_hpp_test.cpp ; -compile info_tuple_hpp_test.cpp ; -compile to_string_hpp_test.cpp ; -compile to_string_stub_hpp_test.cpp ; compile all_hpp_test.cpp ; compile current_exception_cast_hpp_test.cpp ; +compile diagnostic_information_hpp_test.cpp ; compile errinfo_api_function_hpp_test.cpp ; compile errinfo_at_line_hpp_test.cpp ; compile errinfo_errno_hpp_test.cpp ; @@ -73,3 +68,10 @@ compile errinfo_file_name_hpp_test.cpp ; compile errinfo_file_open_mode_hpp_test.cpp ; compile errinfo_nested_exception_hpp_test.cpp ; compile errinfo_type_info_name_hpp_test.cpp ; +compile error_info_hpp_test.cpp ; +compile exception_ptr_hpp_test.cpp ; +compile get_error_info_hpp_test.cpp ; +compile info_hpp_test.cpp ; +compile info_tuple_hpp_test.cpp ; +compile to_string_hpp_test.cpp ; +compile to_string_stub_hpp_test.cpp ; diff --git a/test/boost_json_test.cpp b/test/boost_json_test.cpp new file mode 100644 index 0000000..eef9d6c --- /dev/null +++ b/test/boost_json_test.cpp @@ -0,0 +1,148 @@ +//Copyright (c) 2006-2026 Emil Dotchevski and Reverge Studios, Inc. + +//Distributed under the Boost Software License, Version 1.0. (See accompanying +//file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include + +#if defined( BOOST_NO_EXCEPTIONS ) +# error This program requires exception handling. +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +using output_encoder = boost::exception_serialization::boost_json_encoder; + +namespace +boost + { + namespace + exception_serialization + { + template + void + serialize(Handle & h, E const & e, char const * name) + { + h.dispatch( + [&](boost_json_encoder & enc) { output_at(enc, e, name); } ); + } + } + } + +typedef boost::error_info my_error1; +typedef boost::error_info my_error2; + +struct +my_info + { + int code; + char const * message; + + friend + void + tag_invoke(boost::json::value_from_tag, boost::json::value & v, my_info const & e) + { + v = { + {"code", e.code}, + {"message", e.message} + }; + } + }; + +typedef boost::error_info my_error3; + +struct +test_exception: + virtual boost::exception, + virtual std::exception + { + char const * + what() const noexcept override + { + return "test_exception::what"; + } + }; + +void +check_output(boost::json::value const & j, bool has_source_location) + { + auto const & obj = j.as_object(); + if( has_source_location ) + { + BOOST_TEST(obj.contains("throw_file")); + BOOST_TEST(obj.contains("throw_line")); + BOOST_TEST(obj.contains("throw_function")); + } +#ifndef BOOST_NO_RTTI + BOOST_TEST(obj.contains("dynamic_exception_type")); +#endif + BOOST_TEST(obj.contains("std::exception::what")); + BOOST_TEST_EQ(obj.at("std::exception::what").as_string(), "test_exception::what"); + + BOOST_TEST(obj.contains("my_error1_")); + BOOST_TEST_EQ(obj.at("my_error1_").as_int64(), 42); + + BOOST_TEST(obj.contains("my_error2_")); + BOOST_TEST_EQ(obj.at("my_error2_").as_string(), "hello"); + + BOOST_TEST(obj.contains("my_error3_")); + auto const & mij = obj.at("my_error3_").as_object(); + BOOST_TEST_EQ(mij.at("code").as_int64(), 1); + BOOST_TEST_EQ(mij.at("message").as_string(), "error one"); + } + +int +main() + { + { + std::cout << "Testing serialize_diagnostic_information_to:\n"; + boost::json::value j; + try + { + test_exception e; + e << + my_error1(42) << + my_error2("hello") << + my_error3({1, "error one"}); + BOOST_THROW_EXCEPTION(e); + } + catch( test_exception & e ) + { + output_encoder enc{j}; + boost::serialize_diagnostic_information_to(e, enc); + } + std::cout << j << std::endl; + check_output(j, true); + } + + { + std::cout << "\nTesting serialize_current_exception_diagnostic_information_to:\n"; + boost::json::value j; + try + { + test_exception e; + e << + my_error1(42) << + my_error2("hello") << + my_error3({1, "error one"}); + BOOST_THROW_EXCEPTION(e); + } + catch( ... ) + { + output_encoder enc{j}; + boost::serialize_current_exception_diagnostic_information_to(enc); + } + std::cout << j << std::endl; + check_output(j, true); + } + + return boost::report_errors(); + } diff --git a/test/nlohmann_test.cpp b/test/nlohmann_json_test.cpp similarity index 55% rename from test/nlohmann_test.cpp rename to test/nlohmann_json_test.cpp index f09d5e2..9269f09 100644 --- a/test/nlohmann_test.cpp +++ b/test/nlohmann_json_test.cpp @@ -10,7 +10,7 @@ #endif #include -#include +#include #include #include "nlohmann/json.hpp" @@ -18,28 +18,30 @@ #include #include #include +#include -using output_writer = boost::exception_serialization::nlohmann_writer; +using output_encoder = boost::exception_serialization::nlohmann_json_encoder; -namespace boost { namespace exception_serialization { - -template -void -serialize(Handle & h, E const & e, char const * name) +namespace +boost { - h.dispatch( - [&](nlohmann_writer & w) { write_nested(w, e, name); }, - [&](nlohmann_writer & w) { write_nested(w, e, name); } - ); + namespace + exception_serialization + { + template + void + serialize(Handle & h, E const & e, char const * name) + { + h.dispatch( + [&](nlohmann_json_encoder & enc) { output_at(enc, e, name); }, + [&](nlohmann_json_encoder & enc) { output_at(enc, e, name); } ); + } + } } -} } - -struct my_error_tag1; -struct my_error_tag2; - -typedef boost::error_info my_error1; -typedef boost::error_info my_error2; +typedef boost::error_info my_error1; +typedef boost::error_info my_error2; +typedef boost::error_info> my_error4; struct my_info @@ -57,8 +59,7 @@ my_info } }; -struct my_info_tag; -typedef boost::error_info my_error3; +typedef boost::error_info my_error3; struct test_exception: @@ -87,23 +88,30 @@ check_output(nlohmann::ordered_json const & j, bool has_source_location) BOOST_TEST(j.contains("std::exception::what")); BOOST_TEST_EQ(j["std::exception::what"].get(), "test_exception::what"); - BOOST_TEST(j.contains("my_error_tag1")); - BOOST_TEST_EQ(j["my_error_tag1"].get(), 42); + BOOST_TEST(j.contains("my_error1_")); + BOOST_TEST_EQ(j["my_error1_"].get(), 42); - BOOST_TEST(j.contains("my_error_tag2")); - BOOST_TEST_EQ(j["my_error_tag2"].get(), "hello"); + BOOST_TEST(j.contains("my_error2_")); + BOOST_TEST_EQ(j["my_error2_"].get(), "hello"); - BOOST_TEST(j.contains("my_info_tag")); - auto const & mij = j["my_info_tag"]; + BOOST_TEST(j.contains("my_error3_")); + auto const & mij = j["my_error3_"]; BOOST_TEST_EQ(mij["code"].get(), 1); BOOST_TEST_EQ(mij["message"].get(), "error one"); + + BOOST_TEST(j.contains("my_error4_")); + auto const & vec = j["my_error4_"]; + BOOST_TEST_EQ(vec.size(), 3u); + BOOST_TEST_EQ(vec[0].get(), 1); + BOOST_TEST_EQ(vec[1].get(), 2); + BOOST_TEST_EQ(vec[2].get(), 3); } int main() { { - std::cout << "Testing write_diagnostic_information_to:\n"; + std::cout << "Testing serialize_diagnostic_information_to:\n"; nlohmann::ordered_json j; try { @@ -111,20 +119,21 @@ main() e << my_error1(42) << my_error2("hello") << - my_error3({1, "error one"}); + my_error3({1, "error one"}) << + my_error4({1, 2, 3}); BOOST_THROW_EXCEPTION(e); } catch( test_exception & e ) { - output_writer w{j}; - boost::write_diagnostic_information_to(e, w); + output_encoder enc{j}; + boost::serialize_diagnostic_information_to(e, enc); } std::cout << std::setw(2) << j << std::endl; check_output(j, true); } { - std::cout << "\nTesting write_current_exception_diagnostic_information_to:\n"; + std::cout << "\nTesting serialize_current_exception_diagnostic_information_to:\n"; nlohmann::ordered_json j; try { @@ -132,13 +141,14 @@ main() e << my_error1(42) << my_error2("hello") << - my_error3({1, "error one"}); + my_error3({1, "error one"}) << + my_error4({1, 2, 3}); BOOST_THROW_EXCEPTION(e); } catch( ... ) { - output_writer w{j}; - boost::write_current_exception_diagnostic_information_to(w); + output_encoder enc{j}; + boost::serialize_current_exception_diagnostic_information_to(enc); } std::cout << std::setw(2) << j << std::endl; check_output(j, true);