// balxml_errorinfo.h                                                 -*-C++-*-
#ifndef INCLUDED_BALXML_ERRORINFO
#define INCLUDED_BALXML_ERRORINFO

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide common error information for XML components.
//
//@CLASSES:
//   balxml::ErrorInfo: details of XML parsing errors
//
//@SEE_ALSO: balxml_reader
//
//@DESCRIPTION: This component provides an in-core value-semantic class,
// `balxml::ErrorInfo`, that contains the following diagnostic information:
// ```
// - severity  (WARNING, ERROR, FATAL ERROR)
// - line number
// - column number
// - document source (name of document containing the error)
// - error message (error description)
// ```
// Parsing components in the `baexml` package make use of `balxml::ErrorInfo`
// as a standard way to report errors and warnings back to the caller.  The
// information contained within a `balxml::ErrorInfo` object is sufficient to
// report a single diagnostic in an input document.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Basic Usage
/// - - - - - - - - - - -
// In this example, we create a parser for a simple file of percentages.  The
// file is formatted as a sequence of lines, with each line containing a
// decimal number in the range "0" to "100", inclusive.  Leading whitespace and
// blank lines are ignored.  When an error occurs during parsing, the error
// data is stored in a `balxml::ErrorInfo` object.  Our parser's interface is
// as follows:
// ```
// #include <balxml_errorinfo.h>
// #include <sstream>
// #include <cstdlib>
// #include <cstring>
// #include <climits>
//
// class PercentParser {
//     // Parse a document stream consisting of a sequence of integral
//     // percentages (0 to 100) in decimal text format, one per line.
//
//     bsl::istream *d_input;    // Input document stream
//     bsl::string   d_docName;  // Document name
//     int           d_line;     // Current line number
//
//   public:
//     PercentParser(bsl::istream       *input,
//                   const bsl::string&  docName = "INPUT");
//         // Construct a parser to parse the data in the specified 'input'
//         // stream having the (optional) specified 'docName'.  A valid
//         // 'input' stream contains a sequence of integers in the range 0
//         // to 100, one per line, in decimal text format.  Each line may
//         // contain leading but not trailing tabs and spaces.  Characters
//         // after the 20th character on each line are ignored and will
//         // generate a warning.
//
//     int parseNext(balxml::ErrorInfo *errorInfo);
//         // Read and parse the next percentage in the input stream and
//         // return the percentage or -1 on eof or -2 on error.  Set the
//         // value of the specified 'errorInfo' structure on error or
//         // warning and leave it unchanged otherwise.  Do nothing and
//         // return -2 if 'errorInfo->severity()' >= 'BAEXML_ERROR'.
// };
// ```
// The constructor is straight-forward:
// ```
// PercentParser::PercentParser(bsl::istream       *input,
//                              const bsl::string&  docName)
// : d_input(input), d_docName(docName), d_line(0)
// {
// }
// ```
// The `parseNext` function begins by testing if a previous error occurred.  By
// testing this condition, we can call `parseNext` several times, knowing that
// the first error will stop the parse operation.
// ```
// int PercentParser::parseNext(balxml::ErrorInfo *errorInfo)
// {
//     static const int MAX_LINE = 20;
//
//     if (errorInfo->isAnyError()) {
//         // Don't advance if errorInfo shows a previous error.
//         return -2;
//     }
// ```
// The parser skips leading whitespace and lines containing only whitespace.
// It loops until a non-empty line is found:
// ```
//     char buffer[MAX_LINE + 1];
//     buffer[0] = '\0';
//
//     // Skip empty lines empty line
//     int len = 0;
//     int startColumn = 0;
//     while (startColumn == len) {
//         ++d_line;
//         d_input->getline(buffer, MAX_LINE + 1, '\n');
//         len = bsl::strlen(buffer);
// ```
// The input stream reports that the input line is longer than `MAX_LINE` by
// setting the fail() condition.  In this case, we set the error object to a
// warning state, indicating the line and column where the problem occurred.
// Then we clear the stream condition and discard the rest of the line.
// ```
//         if (MAX_LINE == len && d_input->fail()) {
//             // 20 characters read without encountering newline.
//             // Warn about long line and discard rest of line.
//             errorInfo->setError(balxml::ErrorInfo::BAEXML_WARNING,
//                                 d_line, len, d_docName,
//                                 "Text after 20th column was discarded");
//             d_input->clear();
//             d_input->ignore(INT_MAX, '\n');
//         }
// ```
// If we detect an EOF condition, we just return -1.  Otherwise, we skip the
// leading whitespace and go on.
// ```
//         else if (0 == len && d_input->eof()) {
//             // Encountered eof before any other characters.
//             return -1;
//         }
//
//         // Skip leading whitespace
//         startColumn = bsl::strspn(buffer, " \t");
//     }
//
// ```
// Now we perform two more error checks: one or superfluous characters after
// the integer, the other for an out-of-range integer.  If the `errorInfo`
// object is already in warning state, either of these errors will overwrite
// the existing warning with the new error condition.
// ```
//     char *endp = 0;
//     long result = bsl::strtol(buffer + startColumn, &endp, 10);
//     int endColumn = endp - buffer;
//     if (endColumn < len) {
//         // Conversion did not consume rest of buffer.
//         errorInfo->setError(balxml::ErrorInfo::BAEXML_ERROR,
//                             d_line, endColumn + 1, d_docName,
//                             "Bad input character");
//         return -2;
//     } else if (result < 0 || 100 < result) {
//         // Range error.
//         errorInfo->setError(balxml::ErrorInfo::BAEXML_ERROR,
//                             d_line, startColumn + 1, d_docName,
//                             "Value is not between 0 and 100");
//         return -2;
//     }
// ```
// If there were no errors, return the result.  Note that the `errorInfo`
// object may contain a warning, but warnings typically do not cause a change
// in the error value.
// ```
//     return result;
// }
// ```
// The main program uses the `PercentParser` class to parse a list of values
// and compute the average.  Typically, the data would be stored in a file,
// but we'll use a literal string for demonstration purposes:
// ```
// int main()
// {
//     static const char INPUTS[] =
//         "    20\n"                  // OK
//         "                   30\n"   // Warning ('0' truncated)
//         "  \n"                      // Skipped: empty line
//         "99x\n"                     // Error: bad character
//         "     101\n"                // Error: out of range
//         "                 1010\n";  // Out-of-range overrides warning
// ```
// We convert the string into a stream and initialize the parser.  We name our
// input stream "Inputs" for the purpose of error handling.  We also
// initialize our working variables:
// ```
//     bsl::istringstream inputstream(INPUTS);
//     PercentParser parser(&inputstream, "Inputs");
//     int result;
//     int sum = 0;
//     int numValues = 0;
// ```
// Any error in parsing will be stored in the `errorInfo` object.  When first
// constructed, it has a severity of `BAEXML_NO_ERROR`.
// ```
//     balxml::ErrorInfo errorInfo;
//     assert(errorInfo.isNoError());
// ```
// Normally, parsing would proceed in a loop.  However, to illustrate the
// different error-handling situations, we have unrolled the loop below.
//
// The first parse succeeds, and no error is reported:
// ```
//     result = parser.parseNext(&errorInfo);
//     assert(20 == result);
//     assert(errorInfo.isNoError());
//     sum += result;
//     ++numValues;
// ```
// The next parse also succeeds but, because the input line was very long, a
// warning was generated:
// ```
//     result = parser.parseNext(&errorInfo);
//     assert(3 == result);  // Truncated at 20th column
//     assert(errorInfo.isWarning());
//     assert(2 == errorInfo.lineNumber());
//     assert(20 == errorInfo.columnNumber());
//     assert("Text after 20th column was discarded" == errorInfo.message());
//     sum += result;
//     ++numValues;
// ```
// After resetting the `errorInfo` object, the we call `nextParse` again.  This
// time it fails with an error.  The line, column, and source of the error are
// reported in the object.
// ```
//     errorInfo.reset();
//     result = parser.parseNext(&errorInfo);
//     assert(-2 == result);
//     assert("Inputs" == errorInfo.source());
//     assert(errorInfo.isError());
//     assert(4 == errorInfo.lineNumber());
//     assert(3 == errorInfo.columnNumber());
//     assert("Bad input character" == errorInfo.message());
// ```
// If the `errorInfo` object is not reset, calling `parseNext` becomes a
// no-op:
// ```
//     result = parser.parseNext(&errorInfo);
//     assert(-2 == result);
//     assert(errorInfo.isError());
//     assert(4 == errorInfo.lineNumber());
//     assert(3 == errorInfo.columnNumber());
//     assert("Bad input character" == errorInfo.message());
// ```
// After calling `reset`, the next call to `parseNext` produces a different
// error message:
// ```
//     errorInfo.reset();
//     result = parser.parseNext(&errorInfo);
//     assert(-2 == result);
//     assert(errorInfo.isError());
//     assert(5 == errorInfo.lineNumber());
//     assert(6 == errorInfo.columnNumber());
//     assert("Value is not between 0 and 100" == errorInfo.message());
// ```
// The last line of the file contains two problems: a long line, which would
// produce a warning, and a range error, which would produce an error.  The
// warning message is overwritten by the error message because the error has a
// higher severity.  Therefore, on return from `parseNext`, only the error
// message is stored in `errorInfo` and the warning is lost:
// ```
//     errorInfo.reset();
//     result = parser.parseNext(&errorInfo);
//     assert(-2 == result);
//     assert(errorInfo.isError());
//     assert(6 == errorInfo.lineNumber());
//     assert(18 == errorInfo.columnNumber());
//     assert("Value is not between 0 and 100" == errorInfo.message());
// ```
// Writing the `errorInfo` object to a log or file will produce a readable
// error message:
// ```
//     bsl::cerr << errorInfo << bsl::endl;
// ```
// The resulting message to standard error looks as follows:
// ```
// Inputs:6.18: Error: Value is not between 0 and 100
// ```
// Finally, we reach the end of the input stream and can compute our average.
// ```
//     errorInfo.reset();
//     result = parser.parseNext(&errorInfo);
//     assert(-1 == result);
//     assert(errorInfo.isNoError());
//
//     int average = sum / numValues;
//     assert(11 == average);  // (20 + 3) / 2
//
//     return 0;
// }
// ```

#include <balscm_version.h>

#include <bslma_allocator.h>
#include <bslma_usesbslmaallocator.h>

#include <bslmf_nestedtraitdeclaration.h>

#include <bsl_ostream.h>
#include <bsl_string.h>

namespace BloombergLP  {
namespace balxml {

                              // ===============
                              // class ErrorInfo
                              // ===============

/// This class provides detailed information for errors encountered during
/// XML parsing.  Such information is common for most parsers and contains
/// the following data: line number, column number, severity code,
/// identification of source document and the parser error message.
class ErrorInfo
{

  public:
    // PUBLIC TYPES
    enum Severity
    {
        // Error severity level.  Each severity level is considered more severe
        // than the one before.  Client software will typically continue
        // processing on 'BAEXML_WARNING' and will stop processing on
        // 'BAEXML_ERROR' or 'BAEXML_FATAL_ERROR'.  The distinction between the
        // latter two severities is somewhat arbitrary.  A component that sets
        // the severity of a 'ErrorInfo' object can use 'BAEXML_FATAL_ERROR' to
        // discard a less-severe error with 'BAEXML_ERROR'.  As a general
        // guideline, 'BAEXML_ERROR' means that processing could continue,
        // albeit with compromised results and 'BAEXML_FATAL_ERROR' means that
        // processing could not continue.  For example, a constraint error
        // would typically have 'BAEXML_ERROR' whereas a parsing (syntax) error
        // would have 'BAEXML_FATAL_ERROR'.
        e_NO_ERROR,
        e_WARNING,
        e_ERROR,
        e_FATAL_ERROR
#ifndef BDE_OMIT_INTERNAL_DEPRECATED
      , BAEXML_NO_ERROR = e_NO_ERROR
      , BAEXML_WARNING = e_WARNING
      , BAEXML_ERROR = e_ERROR
      , BAEXML_FATAL_ERROR = e_FATAL_ERROR
#endif  // BDE_OMIT_INTERNAL_DEPRECATED
    };

  private:
    // PRIVATE DATA MEMBERS
    Severity          d_severity;
    int               d_lineNumber;
    int               d_columnNumber;
    bsl::string       d_source;
    bsl::string       d_message;

  public:
    // TRAITS
    BSLMF_NESTED_TRAIT_DECLARATION(ErrorInfo, bslma::UsesBslmaAllocator);

    // CREATORS

    /// Construct an error info object using the (optionally) specified
    /// `basicAllocator`, or the default allocator of none is specified.
    /// After construction, `severity()` will return `BAEXML_NO_ERROR`,
    /// `lineNumber()` and `columnNumber()` will each return 0, and
    /// `source()` and `message()` will each return an empty string.
    ErrorInfo(bslma::Allocator *basicAllocator = 0);

    /// Construct a copy of the specified `other` object using the
    /// (optionally) specified `basicAllocator`, or the default allocator
    /// of none is specified.
    ErrorInfo(const ErrorInfo& other, bslma::Allocator *basicAllocator = 0);

    /// Destroy this object.
    ~ErrorInfo();

    // MANIPULATORS

    /// Copy the value of the specified `rhs` object into this object and
    /// return a modifiable reference to this object.
    ErrorInfo& operator=(const ErrorInfo& rhs);

    /// If the specified `severity` is greater than the current value of
    /// `this->severity()`, then set this object's severity to `severity`,
    /// line number to the specified `lineNumber`, column number to the
    /// specified `columnNumber`, source name to the specified `source`, and
    /// error message to the specified `errorMsg`, otherwise do nothing.
    void setError(Severity                severity,
                  int                     lineNumber,
                  int                     columnNumber,
                  const bsl::string_view& source,
                  const bsl::string_view& errorMsg);

    /// If the severity of the specified `other` object is greater than the
    /// current value of `this->severity()`, then assign this object the
    /// value of `other`, otherwise do nothing.
    void setError(const ErrorInfo& other);

    /// Reset this object to initial state, as if it were default
    /// constructed.
    void reset();

    // ACCESSORS

    /// Return true if the `severity() == `BAEXML_NO_ERROR' and false
    /// otherwise.
    bool isNoError() const;

    /// Return true if the `severity() == `BAEXML_WARNING' and false
    /// otherwise.
    bool isWarning() const;

    /// Return true if the `severity() == `BAEXML_ERROR' and false
    /// otherwise.
    bool isError() const;

    /// Return true if the `severity() == `BAEXML_FATAL_ERROR' and false
    /// otherwise.
    bool isFatalError() const;

    /// Return true if the `severity() >= `BAEXML_ERROR' (i.e.,
    /// `BAEXML_ERROR` or `BAEXML_FATAL_ERROR`) and false otherwise.
    bool isAnyError() const;

    /// Return the severity level.
    Severity severity() const;

    /// Return the line number of the warning or error.  By convention, the
    /// first line is numbered 1.  The constructors and `reset` functions
    /// set the line number to 0, since there is no error line to report.
    int lineNumber() const;

    /// Return the column number.  By convention, the first column is
    /// numbered 1.  The constructors and `reset` functions set the column
    /// number to 0, since there is no error column to report.
    int columnNumber() const;

    /// Return the string that identifies the document being parsed.
    const bsl::string& source() const;

    /// Return the string describing the error or warning.
    const bsl::string& message() const;
};

// FREE OPERATORS

/// Return true if the specified `lhs` object has the same value as the
/// specified `rhs` object.  The two objects have the same value if
/// `severity()`, `lineNumber()`, `columnNumber()`, `source()`, and
/// `message()` return equal values for both.
bool operator==(const ErrorInfo& lhs, const ErrorInfo& rhs);

/// Return true if the specified `lhs` object does not have the same value
/// as the specified `rhs` object.  The two objects have the same value if
/// `severity()`, `lineNumber()`, `columnNumber()`, `source()`, and
/// `message()` return equal values for both.
inline
bool operator!=(const ErrorInfo& lhs, const ErrorInfo& rhs);

/// Print the specified `errInfo` object to the specified `stream` in
/// human-readable form and return a modifiable reference to `stream`.  The
/// output is one-line without a terminating newline.
bsl::ostream& operator<<(bsl::ostream& stream, const ErrorInfo& errInfo);

// ============================================================================
//                            INLINE DEFINITIONS
// ============================================================================

                              // ---------------
                              // class ErrorInfo
                              // ---------------
// MANIPULATORS
inline void
ErrorInfo::setError(const ErrorInfo& other)
{
    if (other.d_severity > d_severity) {
        *this = other;
    }
}

// ACCESSORS
inline ErrorInfo::Severity
ErrorInfo::severity() const
{
    return d_severity;
}

inline bool
ErrorInfo::isNoError() const
{
    return d_severity == e_NO_ERROR;
}

inline bool
ErrorInfo::isWarning() const
{
    return d_severity == e_WARNING;
}

inline bool
ErrorInfo::isError() const
{
    return d_severity == e_ERROR;
}

inline bool
ErrorInfo::isFatalError() const
{
    return d_severity == e_FATAL_ERROR;
}

inline bool
ErrorInfo::isAnyError() const
{
    return d_severity >= e_ERROR;
}

inline int
ErrorInfo::lineNumber() const
{
    return d_lineNumber;
}

inline int
ErrorInfo::columnNumber() const
{
    return d_columnNumber;
}

inline const bsl::string &
ErrorInfo::source() const
{
    return d_source;
}

inline const bsl::string &
ErrorInfo::message() const
{
    return d_message;
}

}  // close package namespace

// FREE OPERATORS
inline
bool balxml::operator!=(const ErrorInfo& lhs, const ErrorInfo& rhs)
{
    return ! (lhs == rhs);
}

}  // close enterprise namespace

#endif // INCLUDED_BALXML_ERRORINFO

// ----------------------------------------------------------------------------
// Copyright 2015 Bloomberg Finance L.P.
//
// Licensed 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.
// ----------------------------- END-OF-FILE ----------------------------------
