blob: b28a291c837daf12b331c9ec4d620a7626fb4b8f [file] [log] [blame]
/** @file
Stacking error message handling.
The problem addressed by this library is the ability to pass back
detailed error messages from failures. It is hard to get good
diagnostics because the specific failures and general context are
located in very different stack frames. This library allows local
functions to pass back local messages which can be easily
augmented as the error travels up the stack frame.
This could be done with exceptions but
- That is more effort to implemention
- Generally more expensive.
Each message on a stack contains text and a numeric identifier.
The identifier value zero is reserved for messages that are not
errors so that information can be passed back even in the success
case.
The implementation takes the position that success is fast and
failure is expensive. Therefore it is optimized for the success
path, imposing very little overhead. On the other hand, if an
error occurs and is handled, that is generally so expensive that
optimizations are pointless (although, of course, one should not
be gratuitiously expensive).
The library also provides the @c Rv ("return value") template to
make returning values and status easier. This template allows a
function to return a value and status pair with minimal changes.
The pair acts like the value type in most situations, while
providing access to the status.
Each instance of an erratum is a wrapper class that emulates value
semantics (copy on write). This means passing even large message
stacks is inexpensive, involving only a pointer copy and reference
counter increment and decrement. A success value is represented by
an internal @c NULL so it is even cheaper to copy.
To further ease use, the library has the ability to define @a
sinks. A sink is a function that acts on an erratum when it
becomes unreferenced. The indended use is to send the messages to
an output log. This makes reporting errors to a log from even
deeply nested functions easy while preserving the ability of the
top level logic to control such logging.
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
#include <memory>
#include <string>
#include <iosfwd>
#include <sstream>
#include <deque>
#include "NumericType.h"
#include "IntrusivePtr.h"
namespace ts
{
/** Class to hold a stack of error messages (the "errata").
This is a smart handle class, which wraps the actual data
and can therefore be treated a value type with cheap copy
semantics. Default construction is very cheap.
*/
class Errata
{
protected:
/// Implementation class.
struct Data;
/// Handle for implementation class instance.
typedef IntrusivePtr<Data> ImpPtr;
public:
typedef Errata self; /// Self reference type.
/// Message ID.
typedef NumericType<unsigned int, struct MsgIdTag> Id;
/* Tag / level / code severity.
This is intended for clients to use to provide additional
classification of a message. A severity code, as for syslog,
is a common use.
*/
typedef NumericType<unsigned int, struct CodeTag> Code;
struct Message;
typedef std::deque<Message> Container; ///< Storage type for messages.
// We iterate backwards to look like a stack.
// typedef Container::reverse_iterator iterator; ///< Message iteration.
/// Message const iteration.
// typedef Container::const_reverse_iterator const_iterator;
/// Reverse message iteration.
// typedef Container::iterator reverse_iterator;
/// Reverse constant message iteration.
// typedef Container::const_iterator const_reverse_iterator;
/// Default constructor - empty errata, very fast.
Errata();
/// Copy constructor, very fast.
Errata(self const &that ///< Object to copy
);
/// Construct from string.
/// Message Id and Code are default.
explicit Errata(std::string const &text ///< Finalized message text.
);
/// Construct with @a id and @a text.
/// Code is default.
Errata(Id id, ///< Message id.
std::string const &text ///< Message text.
);
/// Construct with @a id, @a code, and @a text.
Errata(Id id, ///< Message text.
Code code, ///< Message code.
std::string const &text ///< Message text.
);
/** Construct from a message instance.
This is equivalent to default constructing an @c errata and then
invoking @c push with an argument of @a msg.
*/
Errata(Message const &msg ///< Message to push
);
/// Move constructor.
Errata(self &&that);
/// Move constructor from @c Message.
Errata(Message &&msg);
/// destructor
~Errata();
/// Self assignment.
/// @return A reference to this object.
self &operator=(const self &that ///< Source instance.
);
/// Move assignment.
self &operator=(self &&that);
/** Assign message.
All other messages are discarded.
@return A reference to this object.
*/
self &operator=(Message const &msg ///< Source message.
);
/** Push @a text as a message.
The message is constructed from just the @a text.
It becomes the top message.
@return A reference to this object.
*/
self &push(std::string const &text);
/** Push @a text as a message with message @a id.
The message is constructed from @a text and @a id.
It becomes the top message.
@return A reference to this object.
*/
self &push(Id id, std::string const &text);
/** Push @a text as a message with message @a id and @a code.
The message is constructed from @a text and @a id.
It becomes the top message.
@return A reference to this object.
*/
self &push(Id id, Code code, std::string const &text);
/** Push a message.
@a msg becomes the top message.
@return A reference to this object.
*/
self &push(Message const &msg);
self &push(Message &&msg);
/** Push a constructed @c Message.
The @c Message is set to have the @a id and @a code. The other arguments are converted
to strings and concatenated to form the messsage text.
@return A reference to this object.
*/
template <typename... Args> self &push(Id id, Code code, Args const &... args);
/** Push a nested status.
@a err becomes the top item.
@return A reference to this object.
*/
self &push(self const &err);
/** Access top message.
@return If the errata is empty, a default constructed message
otherwise the most recent message.
*/
Message const &top() const;
/** Move messages from @a that to @c this errata.
Messages from @a that are put on the top of the
stack in @c this and removed from @a that.
*/
self &pull(self &that);
/// Remove last message.
void pop();
/// Remove all messages.
void clear();
/** Inhibit logging.
@note This only affects @c this as a top level @c errata.
It has no effect on this @c this being logged as a nested
@c errata.
*/
self &doNotLog();
friend std::ostream &operator<<(std::ostream &, self const &);
/// Default glue value (a newline) for text rendering.
static std::string const DEFAULT_GLUE;
/** Test status.
Equivalent to @c success but more convenient for use in
control statements.
@return @c true if no messages or last message has a zero
message ID, @c false otherwise.
*/
operator bool() const;
/** Test errata for no failure condition.
Equivalent to @c operator @c bool but easier to invoke.
@return @c true if no messages or last message has a zero
message ID, @c false otherwise.
*/
bool isOK() const;
/// Number of messages in the errata.
size_t size() const;
/* Forward declares.
We have to make our own iterators as the least bad option. The problem
is that we have recursive structures so declaration order is difficult.
We can't use the container iterators here because the element type is
not yet defined. If we define the element type here, it can't contain
an Errata and we have to do funky things to get around that. So we
have our own iterators, which are just shadowing sublclasses of the
container iterators.
*/
class iterator;
class const_iterator;
/// Reference to top item on the stack.
iterator begin();
/// Reference to top item on the stack.
const_iterator begin() const;
//! Reference one past bottom item on the stack.
iterator end();
//! Reference one past bottom item on the stack.
const_iterator end() const;
// Logging support.
/** Base class for erratum sink.
When an errata is abandoned, this will be called on it to perform
any client specific logging. It is passed around by handle so that
it doesn't have to support copy semantics (and is not destructed
until application shutdown). Clients can subclass this class in order
to preserve arbitrary data for the sink or retain a handle to the
sink for runtime modifications.
*/
class Sink : public IntrusivePtrCounter
{
public:
typedef Sink self; ///< Self reference type.
typedef IntrusivePtr<self> Handle; ///< Handle type.
/// Handle an abandoned errata.
virtual void operator()(Errata const &) const = 0;
/// Force virtual destructor.
virtual ~Sink() {}
};
//! Register a sink for discarded erratum.
static void registerSink(Sink::Handle const &s);
/// Register a function as a sink.
typedef void (*SinkHandlerFunction)(Errata const &);
// Wrapper class to support registering functions as sinks.
struct SinkFunctionWrapper : public Sink {
/// Constructor.
SinkFunctionWrapper(SinkHandlerFunction f) : m_f(f) {}
/// Operator to invoke the function.
void
operator()(Errata const &e) const override
{
m_f(e);
}
SinkHandlerFunction m_f; ///< Client supplied handler.
};
/// Register a sink function for abandonded erratum.
static void
registerSink(SinkHandlerFunction f)
{
registerSink(Sink::Handle(new SinkFunctionWrapper(f)));
}
/** Simple formatted output.
Each message is written to a line. All lines are indented with
whitespace @a offset characters. Lines are indented an
additional @a indent. This value is increased by @a shift for
each level of nesting of an @c Errata. if @a lead is not @c
NULL the indentation is overwritten by @a lead if @a indent is
non-zero. It acts as a "continuation" marker for nested
@c Errata.
*/
std::ostream &write(std::ostream &out, ///< Output stream.
int offset, ///< Lead white space for every line.
int indent, ///< Additional indention per line for messages.
int shift, ///< Additional @a indent for nested @c Errata.
char const *lead ///< Leading text for nested @c Errata.
) const;
/// Simple formatted output to fixed sized buffer.
/// @return Number of characters written to @a buffer.
size_t write(char *buffer, ///< Output buffer.
size_t n, ///< Buffer size.
int offset, ///< Lead white space for every line.
int indent, ///< Additional indention per line for messages.
int shift, ///< Additional @a indent for nested @c Errata.
char const *lead ///< Leading text for nested @c Errata.
) const;
protected:
/// Construct from implementation pointer.
/// Used internally by nested classes.
Errata(ImpPtr const &ptr);
/// Implementation instance.
ImpPtr m_data;
/// Return the implementation instance, allocating and unsharing as needed.
Data *pre_write();
/// Force and return an implementation instance.
/// Does not follow copy on write.
Data const *instance();
/// Used for returns when no data is present.
static Message const NIL_MESSAGE;
friend struct Data;
friend class Item;
};
extern std::ostream &operator<<(std::ostream &os, Errata const &stat);
/// Storage for a single message.
struct Errata::Message {
typedef Message self; ///< Self reference type.
/// Default constructor.
/// The message has Id = 0, default code, and empty text.
Message() = default;
/// Construct from text.
/// Id is zero and Code is default.
Message(std::string const &text ///< Finalized message text.
);
/// Construct with @a id and @a text.
/// Code is default.
Message(Id id, ///< ID of message in table.
std::string const &text ///< Final text for message.
);
/// Construct with @a id, @a code, and @a text.
Message(Id id, ///< Message Id.
Code code, ///< Message Code.
std::string const &text ///< Final text for message.
);
/// Construct with an @a id, @a code, and a @a message.
/// The message contents are created by converting the variable arguments
/// to strings using the stream operator and concatenated in order.
template <typename... Args>
Message(Id id, ///< Message Id.
Code code, ///< Message Code.
Args const &... text);
/// Reset to the message to default state.
self &clear();
/// Set the message Id.
self &set(Id id ///< New message Id.
);
/// Set the code.
self &set(Code code ///< New code for message.
);
/// Set the text.
self &set(std::string const &text ///< New message text.
);
/// Set the text.
self &set(char const *text ///< New message text.
);
/// Set the errata.
self &set(Errata const &err ///< Errata to store.
);
/// Get the text of the message.
std::string const &text() const;
/// Get the code.
Code getCode() const;
/// Get the nested status.
/// @return A status object, which is not @c NULL if there is a
/// nested status stored in this item.
Errata getErrata() const;
/** The default message code.
This value is used as the Code value for constructing and
clearing messages. It can be changed to control the value
used for empty messages.
*/
static Code Default_Code;
/// Type for overriding success message test.
typedef bool (*SuccessTest)(Message const &m);
/** Success message test.
When a message is tested for being "successful", this
function is called. It may be overridden by a client.
The initial value is @c DEFAULT_SUCCESS_TEST.
@note This is only called when there are Messages in the
Errata. An empty Errata (@c NULL or empty stack) is always
a success. Only the @c top Message is checked.
@return @c true if the message indicates success,
@c false otherwise.
*/
static SuccessTest Success_Test;
/// Indicate success if the message code is zero.
/// @note Used as the default success test.
static bool isCodeZero(Message const &m);
static SuccessTest const DEFAULT_SUCCESS_TEST;
template <typename... Args> static std::string stringify(Args const &... items);
Id m_id = 0; ///< Message ID.
Code m_code = Default_Code; ///< Message code.
std::string m_text; ///< Final text.
Errata m_errata; ///< Nested errata.
};
/** This is the implementation class for Errata.
It holds the actual messages and is treated as a passive data
object with nice constructors.
We implement reference counting semantics by hand for two
reasons. One is that we need to do some specialized things, but
mainly because the client can't see this class so we can't
*/
struct Errata::Data : public IntrusivePtrCounter {
typedef Data self; ///< Self reference type.
//! Default constructor.
Data();
/// Destructor, to do logging.
~Data();
//! Number of messages.
size_t size() const;
/// Get the top message on the stack.
Message const &top() const;
/// Put a message on top of the stack.
void push(Message const &msg);
void push(Message &&msg);
/// Log this when it is deleted.
mutable bool m_log_on_delete = true;
//! The message stack.
Container m_items;
};
/// Forward iterator for @c Messages in an @c Errata.
class Errata::iterator : public Errata::Container::reverse_iterator
{
public:
typedef iterator self; ///< Self reference type.
typedef Errata::Container::reverse_iterator super; ///< Parent type.
iterator(); ///< Default constructor.
/// Copy constructor.
iterator(self const &that ///< Source instance.
);
/// Construct from super class.
iterator(super const &that ///< Source instance.
);
/// Assignment.
self &operator=(self const &that);
/// Assignment from super class.
self &operator=(super const &that);
/// Prefix increment.
self &operator++();
/// Prefix decrement.
self &operator--();
};
/// Forward constant iterator for @c Messages in an @c Errata.
class Errata::const_iterator : public Errata::Container::const_reverse_iterator
{
public:
typedef const_iterator self; ///< Self reference type.
typedef Errata::Container::const_reverse_iterator super; ///< Parent type.
const_iterator(); ///< Default constructor.
/// Copy constructor.
const_iterator(self const &that ///< Source instance.
);
const_iterator(super const &that ///< Source instance.
);
/// Assignment.
self &operator=(self const &that);
/// Assignment from super class.
self &operator=(super const &that);
/// Prefix increment.
self &operator++();
/// Prefix decrement.
self &operator--();
};
/** Helper class for @c Rv.
This class enables us to move the implementation of non-templated methods
and members out of the header file for a cleaner API.
*/
struct RvBase {
Errata _errata; ///< The status from the function.
/** Default constructor. */
RvBase();
/** Construct with specific status.
*/
RvBase(Errata const &s ///< Status to copy
);
//! Test the return value for success.
bool isOK() const;
/** Clear any stacked errors.
This is useful during shutdown, to silence irrelevant errors caused
by the shutdown process.
*/
void clear();
/// Inhibit logging of the errata.
void doNotLog();
};
/** Return type for returning a value and status (errata). In
general, a method wants to return both a result and a status so
that errors are logged properly. This structure is used to do that
in way that is more usable than just @c std::pair. - Simpler and
shorter typography - Force use of @c errata rather than having to
remember it (and the order) each time - Enable assignment directly
to @a R for ease of use and compatibility so clients can upgrade
asynchronously.
*/
template <typename R> struct Rv : public RvBase {
typedef Rv self; ///< Standard self reference type.
typedef RvBase super; ///< Standard super class reference type.
typedef R Result; ///< Type of result value.
Result _result; ///< The actual result of the function.
/** Default constructor.
The default constructor for @a R is used.
The status is initialized to SUCCESS.
*/
Rv();
/** Standard (success) constructor.
This copies the result and sets the status to SUCCESS.
@note Not @c explicit so that clients can return just a result
and have it be marked as SUCCESS.
*/
Rv(Result const &r ///< The function result
);
/** Construct from a result and a pre-existing status object.
@internal No constructor from just an Errata to avoid
potential ambiguity with constructing from result type.
*/
Rv(Result const &r, ///< The function result
Errata const &s ///< A pre-existing status object
);
/** User conversion to the result type.
This makes it easy to use the function normally or to pass the
result only to other functions without having to extract it by
hand.
*/
operator Result const &() const;
/** Assignment from result type.
This allows the result to be assigned to a pre-declared return
value structure. The return value is a reference to the
internal result so that this operator can be chained in other
assignments to instances of result type. This is most commonly
used when the result is computed in to a local variable to be
both returned and stored in a member.
@code
Rv<int> zret;
int value;
// ... complex computations, result in value
this->m_value = zret = value;
// ...
return zret;
@endcode
@return A reference to the copy of @a r stored in this object.
*/
Result &
operator=(Result const &r ///< Result to assign
)
{
_result = r;
return _result;
}
/** Add the status from another instance to this one.
@return A reference to @c this object.
*/
template <typename U>
self &push(Rv<U> const &that ///< Source of status messages
);
/** Set the result.
This differs from assignment of the function result in that the
return value is a reference to the @c Rv, not the internal
result. This makes it useful for assigning a result local
variable and then returning.
@code
Rv<int> zret;
int value;
// ... complex computation, result in value
return zret.set(value);
@endcode
*/
self &set(Result const &r ///< Result to store
);
/** Return the result.
@return A reference to the result value in this object.
*/
Result &result();
/** Return the result.
@return A reference to the result value in this object.
*/
Result const &result() const;
/** Return the status.
@return A reference to the @c errata in this object.
*/
Errata &errata();
/** Return the status.
@return A reference to the @c errata in this object.
*/
Errata const &errata() const;
/// Directly set the errata
self &operator=(Errata const &status ///< Errata to assign.
);
/// Push a message on to the status.
self &push(Errata::Message const &msg);
};
/** Combine a function result and status in to an @c Rv.
This is useful for clients that want to declare the status object
and result independently.
*/
template <typename R>
Rv<R>
MakeRv(R const &r, ///< The function result
Errata const &s ///< The pre-existing status object
)
{
return Rv<R>(r, s);
}
/* ----------------------------------------------------------------------- */
/* ----------------------------------------------------------------------- */
// Inline methods.
inline Errata::Message::Message(std::string const &text) : m_text(text) {}
inline Errata::Message::Message(Id id, std::string const &text) : m_text(text) {}
inline Errata::Message::Message(Id id, Code code, std::string const &text) : m_text(text) {}
template <typename... Args>
Errata::Message::Message(Id id, Code code, Args const &... text) : m_id(id), m_code(code), m_text(stringify(text...))
{
}
inline Errata::Message &
Errata::Message::clear()
{
m_id = 0;
m_code = Default_Code;
m_text.erase();
m_errata.clear();
return *this;
}
inline std::string const &
Errata::Message::text() const
{
return m_text;
}
inline Errata::Code
Errata::Message::getCode() const
{
return m_code;
}
inline Errata
Errata::Message::getErrata() const
{
return m_errata;
}
inline Errata::Message &
Errata::Message::set(Id id)
{
m_id = id;
return *this;
}
inline Errata::Message &
Errata::Message::set(Code code)
{
m_code = code;
return *this;
}
inline Errata::Message &
Errata::Message::set(std::string const &text)
{
m_text = text;
return *this;
}
inline Errata::Message &
Errata::Message::set(char const *text)
{
m_text = text;
return *this;
}
inline Errata::Message &
Errata::Message::set(Errata const &err)
{
m_errata = err;
m_errata.doNotLog();
return *this;
}
template <typename... Args>
std::string
Errata::Message::stringify(Args const &... items)
{
std::ostringstream s;
(void)(int[]){0, ((s << items), 0)...};
return s.str();
}
inline Errata::Errata() {}
inline Errata::Errata(Id id, Code code, std::string const &text)
{
this->push(Message(id, code, text));
}
inline Errata::Errata(Message const &msg)
{
this->push(msg);
}
inline Errata::Errata(Message &&msg)
{
this->push(std::move(msg));
}
inline Errata::operator bool() const
{
return this->isOK();
}
inline size_t
Errata::size() const
{
return m_data ? m_data->m_items.size() : 0;
}
inline bool
Errata::isOK() const
{
return nullptr == m_data || 0 == m_data->size() || Message::Success_Test(this->top());
}
inline Errata &
Errata::push(std::string const &text)
{
this->push(Message(text));
return *this;
}
inline Errata &
Errata::push(Id id, std::string const &text)
{
this->push(Message(id, text));
return *this;
}
inline Errata &
Errata::push(Id id, Code code, std::string const &text)
{
this->push(Message(id, code, text));
return *this;
}
template <typename... Args>
auto
Errata::push(Id id, Code code, Args const &... args) -> self &
{
this->push(Message(id, code, args...));
return *this;
}
inline Errata::Message const &
Errata::top() const
{
return m_data ? m_data->top() : NIL_MESSAGE;
}
inline Errata &
Errata::doNotLog()
{
this->instance()->m_log_on_delete = false;
return *this;
}
inline Errata::Data::Data() {}
inline size_t
Errata::Data::size() const
{
return m_items.size();
}
inline Errata::iterator::iterator() {}
inline Errata::iterator::iterator(self const &that) : super(that) {}
inline Errata::iterator::iterator(super const &that) : super(that) {}
inline Errata::iterator &
Errata::iterator::operator=(self const &that)
{
this->super::operator=(that);
return *this;
}
inline Errata::iterator &
Errata::iterator::operator=(super const &that)
{
this->super::operator=(that);
return *this;
}
inline Errata::iterator &
Errata::iterator::operator++()
{
this->super::operator++();
return *this;
}
inline Errata::iterator &
Errata::iterator::operator--()
{
this->super::operator--();
return *this;
}
inline Errata::const_iterator::const_iterator() {}
inline Errata::const_iterator::const_iterator(self const &that) : super(that) {}
inline Errata::const_iterator::const_iterator(super const &that) : super(that) {}
inline Errata::const_iterator &
Errata::const_iterator::operator=(self const &that)
{
super::operator=(that);
return *this;
}
inline Errata::const_iterator &
Errata::const_iterator::operator=(super const &that)
{
super::operator=(that);
return *this;
}
inline Errata::const_iterator &
Errata::const_iterator::operator++()
{
this->super::operator++();
return *this;
}
inline Errata::const_iterator &
Errata::const_iterator::operator--()
{
this->super::operator--();
return *this;
}
inline RvBase::RvBase() {}
inline RvBase::RvBase(Errata const &errata) : _errata(errata) {}
inline bool
RvBase::isOK() const
{
return _errata;
}
inline void
RvBase::clear()
{
_errata.clear();
}
inline void
RvBase::doNotLog()
{
_errata.doNotLog();
}
template <typename T> Rv<T>::Rv() : _result() {}
template <typename T> Rv<T>::Rv(Result const &r) : _result(r) {}
template <typename T> Rv<T>::Rv(Result const &r, Errata const &errata) : super(errata), _result(r) {}
template <typename T> Rv<T>::operator Result const &() const
{
return _result;
}
template <typename T>
T const &
Rv<T>::result() const
{
return _result;
}
template <typename T>
T &
Rv<T>::result()
{
return _result;
}
template <typename T>
Errata const &
Rv<T>::errata() const
{
return _errata;
}
template <typename T>
Errata &
Rv<T>::errata()
{
return _errata;
}
template <typename T>
Rv<T> &
Rv<T>::set(Result const &r)
{
_result = r;
return *this;
}
template <typename T>
Rv<T> &
Rv<T>::operator=(Errata const &errata)
{
_errata = errata;
return *this;
}
template <typename T>
Rv<T> &
Rv<T>::push(Errata::Message const &msg)
{
_errata.push(msg);
return *this;
}
template <typename T>
template <typename U>
Rv<T> &
Rv<T>::push(Rv<U> const &that)
{
_errata.push(that.errata());
return *this;
}
/* ----------------------------------------------------------------------- */
/* ----------------------------------------------------------------------- */
} // namespace ts