diff --git a/example/condense/condense.cpp b/example/condense/condense.cpp index 93da389..bad3c91 100644 --- a/example/condense/condense.cpp +++ b/example/condense/condense.cpp @@ -7,6 +7,7 @@ #include "rapidjson/writer.h" #include "rapidjson/filereadstream.h" #include "rapidjson/filewritestream.h" +#include "rapidjson/error/en.h" using namespace rapidjson; @@ -23,7 +24,7 @@ int main(int, char*[]) { // JSON reader parse from the input stream and let writer generate the output. if (!reader.Parse(is, writer)) { - fprintf(stderr, "\nError(%u): %s\n", (unsigned)reader.GetErrorOffset(), reader.GetParseError()); + fprintf(stderr, "\nError(%u): %s\n", (unsigned)reader.GetErrorOffset(), GetParseError_En(reader.GetParseErrorCode())); return 1; } diff --git a/example/pretty/pretty.cpp b/example/pretty/pretty.cpp index b09fc78..cfb3f0f 100644 --- a/example/pretty/pretty.cpp +++ b/example/pretty/pretty.cpp @@ -5,6 +5,7 @@ #include "rapidjson/prettywriter.h" #include "rapidjson/filereadstream.h" #include "rapidjson/filewritestream.h" +#include "rapidjson/error/en.h" using namespace rapidjson; @@ -21,7 +22,7 @@ int main(int, char*[]) { // JSON reader parse from the input stream and let writer generate the output. if (!reader.Parse(is, writer)) { - fprintf(stderr, "\nError(%u): %s\n", (unsigned)reader.GetErrorOffset(), reader.GetParseError()); + fprintf(stderr, "\nError(%u): %s\n", (unsigned)reader.GetErrorOffset(), GetParseError_En(reader.GetParseErrorCode())); return 1; } diff --git a/example/prettyauto/prettyauto.cpp b/example/prettyauto/prettyauto.cpp index 2e52e9e..3f85f40 100644 --- a/example/prettyauto/prettyauto.cpp +++ b/example/prettyauto/prettyauto.cpp @@ -7,6 +7,7 @@ #include "rapidjson/filereadstream.h" #include "rapidjson/filewritestream.h" #include "rapidjson/encodedstream.h" // NEW +#include "rapidjson/error/en.h" #ifdef _WIN32 #include #include @@ -47,7 +48,7 @@ int main(int, char*[]) { // JSON reader parse from the input stream and let writer generate the output. //if (!reader.Parse(is, writer)) { if (!reader.Parse(eis, writer)) { // CHANGED - fprintf(stderr, "\nError(%u): %s\n", (unsigned)reader.GetErrorOffset(), reader.GetParseError()); + fprintf(stderr, "\nError(%u): %s\n", (unsigned)reader.GetErrorOffset(), GetParseError_En(reader.GetParseErrorCode())); return 1; } diff --git a/include/rapidjson/document.h b/include/rapidjson/document.h index 32f71de..dc3533f 100644 --- a/include/rapidjson/document.h +++ b/include/rapidjson/document.h @@ -788,7 +788,7 @@ public: /*! \param allocator Optional allocator for allocating stack memory. \param stackCapacity Initial capacity of stack in bytes. */ - GenericDocument(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity) : stack_(allocator, stackCapacity), parseError_(0), errorOffset_(0) {} + GenericDocument(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity) : stack_(allocator, stackCapacity), parseErrorCode_(kParseErrorNone), errorOffset_(0) {} //! Parse JSON text from an input stream. /*! \tparam parseFlags Combination of ParseFlag. @@ -802,11 +802,11 @@ public: if (reader.template Parse(is, *this)) { RAPIDJSON_ASSERT(stack_.GetSize() == sizeof(ValueType)); // Got one and only one root object this->RawAssign(*stack_.template Pop(1)); // Add this-> to prevent issue 13. - parseError_ = 0; + parseErrorCode_ = kParseErrorNone; errorOffset_ = 0; } else { - parseError_ = reader.GetParseError(); + parseErrorCode_ = reader.GetParseErrorCode(); errorOffset_ = reader.GetErrorOffset(); ClearStack(); } @@ -864,10 +864,10 @@ public: } //! Whether a parse error was occured in the last parsing. - bool HasParseError() const { return parseError_ != 0; } + bool HasParseError() const { return parseErrorCode_ != kParseErrorNone; } //! Get the message of parsing error. - const char* GetParseError() const { return parseError_; } + ParseErrorCode GetParseError() const { return parseErrorCode_; } //! Get the offset in character of the parsing error. size_t GetErrorOffset() const { return errorOffset_; } @@ -927,7 +927,7 @@ private: static const size_t kDefaultStackCapacity = 1024; internal::Stack stack_; - const char* parseError_; + ParseErrorCode parseErrorCode_; size_t errorOffset_; }; diff --git a/include/rapidjson/error/en.h b/include/rapidjson/error/en.h new file mode 100644 index 0000000..45017d3 --- /dev/null +++ b/include/rapidjson/error/en.h @@ -0,0 +1,48 @@ +#ifndef RAPIDJSON_ERROR_EN_H__ +#define RAPIDJSON_ERROR_EN_H__ + +#include "error.h" + +namespace rapidjson { + +//! Maps error code of parsing into error message. +/*! + \param parseErrorCode Error code obtained in parsing. + \return the error message. + \note User can make a copy of this function for localization. + Using switch-case is safer for future modification of error codes. +*/ +inline const RAPIDJSON_ERROR_CHARTYPE* GetParseError_En(ParseErrorCode parseErrorCode) { + switch (parseErrorCode) { + case kParseErrorNone: return RAPIDJSON_ERROR_STRING("No error."); + + case kParseErrorDocumentEmpty: return RAPIDJSON_ERROR_STRING("The document is empty."); + case kParseErrorDocumentRootNotObjectOrArray: return RAPIDJSON_ERROR_STRING("The document root must be either object or array."); + case kParseErrorDocumentRootNotSingular: return RAPIDJSON_ERROR_STRING("The document root must not follow by other values."); + + case kParseErrorValueInvalid: return RAPIDJSON_ERROR_STRING("Invalid value."); + + case kParseErrorObjectMissName: return RAPIDJSON_ERROR_STRING("Missing a name for object member."); + case kParseErrorObjectMissColon: return RAPIDJSON_ERROR_STRING("Missing a colon after a name of object member."); + case kParseErrorObjectMissCommaOrCurlyBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or '}' after an object member."); + + case kParseErrorArrayMissCommaOrSquareBracket: return RAPIDJSON_ERROR_STRING("Missing a comma or ']' after an array element."); + + case kParseErrorStringUnicodeEscapeInvalidHex: return RAPIDJSON_ERROR_STRING("Incorrect hex digit after \\u escape in string."); + case kParseErrorStringUnicodeSurrogateInvalid: return RAPIDJSON_ERROR_STRING("The surrogate pair in string is invalid."); + case kParseErrorStringEscapeInvalid: return RAPIDJSON_ERROR_STRING("Invalid escape character in string."); + case kParseErrorStringMissQuotationMark: return RAPIDJSON_ERROR_STRING("Missing a closing quotation mark in string."); + case kParseErrorStringInvalidEncoding: return RAPIDJSON_ERROR_STRING("Invalid encoidng in string."); + + case kParesErrorNumberTooBig: return RAPIDJSON_ERROR_STRING("Number too big to be stored in double."); + case kParseErrorNumberMissFraction: return RAPIDJSON_ERROR_STRING("Miss fraction part in number."); + case kParseErrorNumberMissExponent: return RAPIDJSON_ERROR_STRING("Miss exponent in number."); + + default: + return RAPIDJSON_ERROR_STRING("Unknown error."); + } +} + +} // namespace rapidjson + +#endif // RAPIDJSON_ERROR_EN_H__ diff --git a/include/rapidjson/error/error.h b/include/rapidjson/error/error.h new file mode 100644 index 0000000..ba45e7e --- /dev/null +++ b/include/rapidjson/error/error.h @@ -0,0 +1,46 @@ +#ifndef RAPIDJSON_ERROR_ERROR_H__ +#define RAPIDJSON_ERROR_ERROR_H__ + +#include "../reader.h" // ParseErrorCode + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ERROR_CHARTYPE + +//! Character type of error messages. +/*! The default charater type is char. + On Windows, user can define this macro as TCHAR for supporting both + unicode/non-unicode settings. +*/ +#ifndef RAPIDJSON_ERROR_CHARTYPE +#define RAPIDJSON_ERROR_CHARTYPE char +#endif + +/////////////////////////////////////////////////////////////////////////////// +// RAPIDJSON_ERROR_STRING + +//! Macro for converting string literial to RAPIDJSON_ERROR_CHARTYPE[]. +/*! By default this conversion macro does nothing. + On Windows, user can define this macro as _T(x) for supporting both + unicode/non-unicode settings. +*/ +#ifndef RAPIDJSON_ERROR_STRING +#define RAPIDJSON_ERROR_STRING(x) x +#endif + +namespace rapidjson { + +//! Function pointer type of GetParseError(). +/*! This is the prototype for GetParseError_X(), where X is a locale. + User can dynamically change locale in runtime, e.g.: + +\code + GetParseErrorFunc GetParseError = GetParseError_En; // or whatever + const RAPIDJSON_ERROR_CHARTYPE* s = GetParseError(document.GetParseErrorCode()); +\endcode +*/ + +typedef const RAPIDJSON_ERROR_CHARTYPE* (*GetParseErrorFunc)(ParseErrorCode); + +} // namespace rapidjson + +#endif // RAPIDJSON_ERROR_ERROR_H__ diff --git a/include/rapidjson/reader.h b/include/rapidjson/reader.h index c772840..a265605 100644 --- a/include/rapidjson/reader.h +++ b/include/rapidjson/reader.h @@ -21,19 +21,18 @@ #endif #ifndef RAPIDJSON_PARSE_ERROR_NORETURN -#define RAPIDJSON_PARSE_ERROR_NORETURN(msg, offset) \ +#define RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode, offset) \ RAPIDJSON_MULTILINEMACRO_BEGIN \ - if (!HasParseError()) {\ - parseError_ = msg; \ - errorOffset_ = offset; \ - }\ -RAPIDJSON_MULTILINEMACRO_END + RAPIDJSON_ASSERT(!HasParseError()); /* Error can only be assigned once */ \ + parseErrorCode_ = parseErrorCode; \ + errorOffset_ = offset; \ + RAPIDJSON_MULTILINEMACRO_END #endif #ifndef RAPIDJSON_PARSE_ERROR -#define RAPIDJSON_PARSE_ERROR(msg, offset) \ +#define RAPIDJSON_PARSE_ERROR(parseErrorCode, offset) \ RAPIDJSON_MULTILINEMACRO_BEGIN \ - RAPIDJSON_PARSE_ERROR_NORETURN(msg, offset); \ + RAPIDJSON_PARSE_ERROR_NORETURN(parseErrorCode, offset); \ return; \ RAPIDJSON_MULTILINEMACRO_END #endif @@ -50,6 +49,33 @@ enum ParseFlag { kParseValidateEncodingFlag = 2 //!< Validate encoding of JSON strings. }; +//! Error code of parsing. +enum ParseErrorCode { + kParseErrorNone = 0, //!< No error. + + kParseErrorDocumentEmpty, //!< The document is empty. + kParseErrorDocumentRootNotObjectOrArray, //!< The document root must be either object or array. + kParseErrorDocumentRootNotSingular, //!< The document root must not follow by other values. + + kParseErrorValueInvalid, //!< Invalid value. + + kParseErrorObjectMissName, //!< Missing a name for object member. + kParseErrorObjectMissColon, //!< Missing a colon after a name of object member. + kParseErrorObjectMissCommaOrCurlyBracket, //!< Missing a comma or '}' after an object member. + + kParseErrorArrayMissCommaOrSquareBracket, //!< Missing a comma or ']' after an array element. + + kParseErrorStringUnicodeEscapeInvalidHex, //!< Incorrect hex digit after \\u escape in string. + kParseErrorStringUnicodeSurrogateInvalid, //!< The surrogate pair in string is invalid. + kParseErrorStringEscapeInvalid, //!< Invalid escape character in string. + kParseErrorStringMissQuotationMark, //!< Missing a closing quotation mark in string. + kParseErrorStringInvalidEncoding, //!< Invalid encoidng in string. + + kParesErrorNumberTooBig, //!< Number too big to be stored in double. + kParseErrorNumberMissFraction, //!< Miss fraction part in number. + kParseErrorNumberMissExponent //!< Miss exponent in number. +}; + /////////////////////////////////////////////////////////////////////////////// // Handler @@ -218,7 +244,7 @@ public: /*! \param allocator Optional allocator for allocating stack memory. (Only use for non-destructive parsing) \param stackCapacity stack capacity in bytes for storing a single decoded string. (Only use for non-destructive parsing) */ - GenericReader(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity) : stack_(allocator, stackCapacity), parseError_(0), errorOffset_(0) {} + GenericReader(Allocator* allocator = 0, size_t stackCapacity = kDefaultStackCapacity) : stack_(allocator, stackCapacity), parseErrorCode_(kParseErrorNone), errorOffset_(0) {} //! Parse JSON text. /*! \tparam parseFlags Combination of ParseFlag. @@ -230,18 +256,18 @@ public: */ template bool Parse(InputStream& is, Handler& handler) { - parseError_ = 0; + parseErrorCode_ = kParseErrorNone; errorOffset_ = 0; SkipWhitespace(is); if (is.Peek() == '\0') - RAPIDJSON_PARSE_ERROR_NORETURN("Text only contains white space(s)", is.Tell()); + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorDocumentEmpty, is.Tell()); else { switch (is.Peek()) { case '{': ParseObject(is, handler); break; case '[': ParseArray(is, handler); break; - default: RAPIDJSON_PARSE_ERROR_NORETURN("Expect either an object or array at root", is.Tell()); + default: RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorDocumentRootNotObjectOrArray, is.Tell()); } if (HasParseError()) goto out; @@ -249,7 +275,7 @@ public: SkipWhitespace(is); if (is.Peek() != '\0') - RAPIDJSON_PARSE_ERROR_NORETURN("Nothing should follow the root object or array.", is.Tell()); + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorDocumentRootNotSingular, is.Tell()); } out: @@ -262,8 +288,10 @@ public: return Parse<0>(is, handler); } - bool HasParseError() const { return parseError_ != 0; } - const char* GetParseError() const { return parseError_; } + bool HasParseError() const { return parseErrorCode_ != kParseErrorNone; } + + ParseErrorCode GetParseErrorCode() const { return parseErrorCode_; } + size_t GetErrorOffset() const { return errorOffset_; } private: @@ -283,7 +311,7 @@ private: for (SizeType memberCount = 0;;) { if (is.Peek() != '"') - RAPIDJSON_PARSE_ERROR("Name of an object member must be a string", is.Tell()); + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissName, is.Tell()); ParseString(is, handler); if (HasParseError()) @@ -292,7 +320,7 @@ private: SkipWhitespace(is); if (is.Take() != ':') - RAPIDJSON_PARSE_ERROR("There must be a colon after the name of object member", is.Tell()); + RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissColon, is.Tell()); SkipWhitespace(is); @@ -307,7 +335,7 @@ private: switch(is.Take()) { case ',': SkipWhitespace(is); break; case '}': handler.EndObject(memberCount); return; - default: RAPIDJSON_PARSE_ERROR("Must be a comma or '}' after an object member", is.Tell()); + default: RAPIDJSON_PARSE_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, is.Tell()); } } } @@ -337,7 +365,7 @@ private: switch (is.Take()) { case ',': SkipWhitespace(is); break; case ']': handler.EndArray(elementCount); return; - default: RAPIDJSON_PARSE_ERROR("Must be a comma or ']' after an array element.", is.Tell()); + default: RAPIDJSON_PARSE_ERROR(kParseErrorArrayMissCommaOrSquareBracket, is.Tell()); } } } @@ -350,7 +378,7 @@ private: if (is.Take() == 'u' && is.Take() == 'l' && is.Take() == 'l') handler.Null(); else - RAPIDJSON_PARSE_ERROR("Invalid value", is.Tell() - 1); + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell() - 1); } template @@ -361,7 +389,7 @@ private: if (is.Take() == 'r' && is.Take() == 'u' && is.Take() == 'e') handler.Bool(true); else - RAPIDJSON_PARSE_ERROR("Invalid value", is.Tell()); + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); } template @@ -372,7 +400,7 @@ private: if (is.Take() == 'a' && is.Take() == 'l' && is.Take() == 's' && is.Take() == 'e') handler.Bool(false); else - RAPIDJSON_PARSE_ERROR("Invalid value", is.Tell() - 1); + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell() - 1); } // Helper function to parse four hexidecimal digits in \uXXXX in ParseString(). @@ -391,7 +419,7 @@ private: else if (c >= 'a' && c <= 'f') codepoint -= 'a' - 10; else { - RAPIDJSON_PARSE_ERROR_NORETURN("Incorrect hex digit after \\u escape", s.Tell() - 1); + RAPIDJSON_PARSE_ERROR_NORETURN(kParseErrorStringUnicodeEscapeInvalidHex, s.Tell() - 1); return 0; } } @@ -468,16 +496,16 @@ private: if (codepoint >= 0xD800 && codepoint <= 0xDBFF) { // Handle UTF-16 surrogate pair if (is.Take() != '\\' || is.Take() != 'u') - RAPIDJSON_PARSE_ERROR("Missing the second \\u in surrogate pair", is.Tell() - 2); + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, is.Tell() - 2); unsigned codepoint2 = ParseHex4(is); if (codepoint2 < 0xDC00 || codepoint2 > 0xDFFF) - RAPIDJSON_PARSE_ERROR("The second \\u in surrogate pair is invalid", is.Tell() - 2); + RAPIDJSON_PARSE_ERROR(kParseErrorStringUnicodeSurrogateInvalid, is.Tell() - 2); codepoint = (((codepoint - 0xD800) << 10) | (codepoint2 - 0xDC00)) + 0x10000; } TEncoding::Encode(os, codepoint); } else - RAPIDJSON_PARSE_ERROR("Unknown escape character", is.Tell() - 1); + RAPIDJSON_PARSE_ERROR(kParseErrorStringEscapeInvalid, is.Tell() - 1); } else if (c == '"') { // Closing double quote is.Take(); @@ -485,14 +513,14 @@ private: return; } else if (c == '\0') - RAPIDJSON_PARSE_ERROR("lacks ending quotation before the end of string", is.Tell() - 1); + RAPIDJSON_PARSE_ERROR(kParseErrorStringMissQuotationMark, is.Tell() - 1); else if ((unsigned)c < 0x20) // RFC 4627: unescaped = %x20-21 / %x23-5B / %x5D-10FFFF - RAPIDJSON_PARSE_ERROR("Incorrect unescaped character in string", is.Tell() - 1); + RAPIDJSON_PARSE_ERROR(kParseErrorStringEscapeInvalid, is.Tell() - 1); else { if (parseFlags & kParseValidateEncodingFlag ? !Transcoder::Validate(is, os) : !Transcoder::Transcode(is, os)) - RAPIDJSON_PARSE_ERROR("Invalid encoding", is.Tell()); + RAPIDJSON_PARSE_ERROR(kParseErrorStringInvalidEncoding, is.Tell()); } } } @@ -539,7 +567,7 @@ private: } } else - RAPIDJSON_PARSE_ERROR("Expect a value here.", is.Tell()); + RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, is.Tell()); // Parse 64bit int uint64_t i64 = 0; @@ -572,7 +600,7 @@ private: d = (double)i64; while (s.Peek() >= '0' && s.Peek() <= '9') { if (d >= 1E307) - RAPIDJSON_PARSE_ERROR("Number too big to store in double", is.Tell()); + RAPIDJSON_PARSE_ERROR(kParesErrorNumberTooBig, is.Tell()); d = d * 10 + (s.Take() - '0'); } } @@ -591,7 +619,7 @@ private: --expFrac; } else - RAPIDJSON_PARSE_ERROR("At least one digit in fraction part", is.Tell()); + RAPIDJSON_PARSE_ERROR(kParseErrorNumberMissFraction, is.Tell()); while (s.Peek() >= '0' && s.Peek() <= '9') { if (expFrac > -16) { @@ -624,11 +652,11 @@ private: while (s.Peek() >= '0' && s.Peek() <= '9') { exp = exp * 10 + (s.Take() - '0'); if (exp > 308) - RAPIDJSON_PARSE_ERROR("Number too big to store in double", is.Tell()); + RAPIDJSON_PARSE_ERROR(kParesErrorNumberTooBig, is.Tell()); } } else - RAPIDJSON_PARSE_ERROR("At least one digit in exponent", s.Tell()); + RAPIDJSON_PARSE_ERROR(kParseErrorNumberMissExponent, s.Tell()); if (expMinus) exp = -exp; @@ -673,7 +701,7 @@ private: static const size_t kDefaultStackCapacity = 256; //!< Default stack capacity in bytes for storing a single decoded string. internal::Stack stack_; //!< A stack for storing decoded string temporarily during non-destructive parsing. - const char* parseError_; + ParseErrorCode parseErrorCode_; size_t errorOffset_; }; // class GenericReader diff --git a/test/unittest/readertest.cpp b/test/unittest/readertest.cpp index bc4e49d..829837d 100644 --- a/test/unittest/readertest.cpp +++ b/test/unittest/readertest.cpp @@ -147,8 +147,8 @@ TEST(Reader, ParseNumberHandler) { #undef TEST_DOUBLE } -TEST(Reader, ParseNumberHandler_Error) { -#define TEST_NUMBER_ERROR(str) \ +TEST(Reader, ParseNumber_Error) { +#define TEST_NUMBER_ERROR(errorCode, str) \ { \ char buffer[1001]; \ sprintf(buffer, "[%s]", str); \ @@ -156,23 +156,27 @@ TEST(Reader, ParseNumberHandler_Error) { BaseReaderHandler<> h; \ Reader reader; \ EXPECT_FALSE(reader.Parse<0>(s, h)); \ + EXPECT_EQ(errorCode, reader.GetParseErrorCode());\ } - TEST_NUMBER_ERROR("a"); // At least one digit in integer part - TEST_NUMBER_ERROR(".1"); // At least one digit in integer part - + // Number too big to be stored in double. { char n1e309[311]; // '1' followed by 309 '0' n1e309[0] = '1'; for (int i = 1; i < 310; i++) n1e309[i] = '0'; n1e309[310] = '\0'; - TEST_NUMBER_ERROR(n1e309); // Number too big to store in double + TEST_NUMBER_ERROR(kParesErrorNumberTooBig, n1e309); } + TEST_NUMBER_ERROR(kParesErrorNumberTooBig, "1e309"); - TEST_NUMBER_ERROR("1."); // At least one digit in fraction part - TEST_NUMBER_ERROR("1e309"); // Number too big to store in double - TEST_NUMBER_ERROR("1e_"); // At least one digit in exponent + // Miss fraction part in number. + TEST_NUMBER_ERROR(kParseErrorNumberMissFraction, "1."); + TEST_NUMBER_ERROR(kParseErrorNumberMissFraction, "1.a"); + + // Miss exponent in number. + TEST_NUMBER_ERROR(kParseErrorNumberMissExponent, "1e"); + TEST_NUMBER_ERROR(kParseErrorNumberMissExponent, "1e_"); #undef TEST_NUMBER_ERROR } @@ -304,27 +308,38 @@ TEST(Reader, ParseString_NonDestructive) { EXPECT_EQ(11u, h.length_); } -bool TestString(const char* str) { +ParseErrorCode TestString(const char* str) { StringStream s(str); BaseReaderHandler<> h; Reader reader; - return reader.Parse(s, h); + reader.Parse(s, h); + return reader.GetParseErrorCode(); } TEST(Reader, ParseString_Error) { +#define TEST_STRING_ERROR(errorCode, str)\ + EXPECT_EQ(errorCode, TestString(str)) + #define ARRAY(...) { __VA_ARGS__ } -#define TEST_STRINGARRAY_ERROR(Encoding, utype, array) \ +#define TEST_STRINGENCODING_ERROR(Encoding, utype, array) \ { \ static const utype ue[] = array; \ static const Encoding::Ch* e = reinterpret_cast(&ue[0]); \ - EXPECT_FALSE(TestString(e)); \ + EXPECT_EQ(kParseErrorStringInvalidEncoding, TestString(e));\ } - EXPECT_FALSE(TestString("[\"\\a\"]")); // Unknown escape character - EXPECT_FALSE(TestString("[\"\\uABCG\"]")); // Incorrect hex digit after \\u escape - EXPECT_FALSE(TestString("[\"\\uD800X\"]")); // Missing the second \\u in surrogate pair - EXPECT_FALSE(TestString("[\"\\uD800\\uFFFF\"]")); // The second \\u in surrogate pair is invalid - EXPECT_FALSE(TestString("[\"Test]")); // lacks ending quotation before the end of string + // Invalid escape character in string. + TEST_STRING_ERROR(kParseErrorStringEscapeInvalid, "[\"\\a\"]"); + + // Incorrect hex digit after \\u escape in string. + TEST_STRING_ERROR(kParseErrorStringUnicodeEscapeInvalidHex, "[\"\\uABCG\"]"); + + // The surrogate pair in string is invalid. + TEST_STRING_ERROR(kParseErrorStringUnicodeSurrogateInvalid, "[\"\\uD800X\"]"); + TEST_STRING_ERROR(kParseErrorStringUnicodeSurrogateInvalid, "[\"\\uD800\\uFFFF\"]"); + + // Missing a closing quotation mark in string. + TEST_STRING_ERROR(kParseErrorStringMissQuotationMark, "[\"Test]"); // http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt @@ -335,9 +350,9 @@ TEST(Reader, ParseString_Error) { char e[] = { '[', '\"', 0, '\"', ']', '\0' }; for (unsigned char c = 0x80u; c <= 0xBFu; c++) { e[2] = c; - bool b = TestString(e); - EXPECT_FALSE(b); - if (b) + ParseErrorCode error = TestString(e); + EXPECT_EQ(kParseErrorStringInvalidEncoding, error); + if (error != kParseErrorStringInvalidEncoding) std::cout << (unsigned)(unsigned char)c << std::endl; } } @@ -347,37 +362,37 @@ TEST(Reader, ParseString_Error) { char e[] = { '[', '\"', 0, ' ', '\"', ']', '\0' }; for (unsigned c = 0xC0u; c <= 0xFFu; c++) { e[2] = (char)c; - EXPECT_FALSE(TestString(e)); + TEST_STRING_ERROR(kParseErrorStringInvalidEncoding, e); } } // 4 Overlong sequences // 4.1 Examples of an overlong ASCII character - TEST_STRINGARRAY_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xC0u, 0xAFu, '\"', ']', '\0')); - TEST_STRINGARRAY_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xE0u, 0x80u, 0xAFu, '\"', ']', '\0')); - TEST_STRINGARRAY_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xF0u, 0x80u, 0x80u, 0xAFu, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xC0u, 0xAFu, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xE0u, 0x80u, 0xAFu, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xF0u, 0x80u, 0x80u, 0xAFu, '\"', ']', '\0')); // 4.2 Maximum overlong sequences - TEST_STRINGARRAY_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xC1u, 0xBFu, '\"', ']', '\0')); - TEST_STRINGARRAY_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xE0u, 0x9Fu, 0xBFu, '\"', ']', '\0')); - TEST_STRINGARRAY_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xF0u, 0x8Fu, 0xBFu, 0xBFu, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xC1u, 0xBFu, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xE0u, 0x9Fu, 0xBFu, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xF0u, 0x8Fu, 0xBFu, 0xBFu, '\"', ']', '\0')); // 4.3 Overlong representation of the NUL character - TEST_STRINGARRAY_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xC0u, 0x80u, '\"', ']', '\0')); - TEST_STRINGARRAY_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xE0u, 0x80u, 0x80u, '\"', ']', '\0')); - TEST_STRINGARRAY_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xF0u, 0x80u, 0x80u, 0x80u, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xC0u, 0x80u, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xE0u, 0x80u, 0x80u, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xF0u, 0x80u, 0x80u, 0x80u, '\"', ']', '\0')); // 5 Illegal code positions // 5.1 Single UTF-16 surrogates - TEST_STRINGARRAY_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xA0u, 0x80u, '\"', ']', '\0')); - TEST_STRINGARRAY_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xADu, 0xBFu, '\"', ']', '\0')); - TEST_STRINGARRAY_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xAEu, 0x80u, '\"', ']', '\0')); - TEST_STRINGARRAY_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xAFu, 0xBFu, '\"', ']', '\0')); - TEST_STRINGARRAY_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xB0u, 0x80u, '\"', ']', '\0')); - TEST_STRINGARRAY_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xBEu, 0x80u, '\"', ']', '\0')); - TEST_STRINGARRAY_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xBFu, 0xBFu, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xA0u, 0x80u, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xADu, 0xBFu, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xAEu, 0x80u, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xAFu, 0xBFu, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xB0u, 0x80u, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xBEu, 0x80u, '\"', ']', '\0')); + TEST_STRINGENCODING_ERROR(UTF8<>, unsigned char, ARRAY('[', '\"', 0xEDu, 0xBFu, 0xBFu, '\"', ']', '\0')); #undef ARRAY #undef TEST_STRINGARRAY_ERROR @@ -416,7 +431,7 @@ TEST(Reader, ParseArray) { } TEST(Reader, ParseArray_Error) { -#define TEST_ARRAY_ERROR(str) \ +#define TEST_ARRAY_ERROR(errorCode, str) \ { \ char buffer[1001]; \ strncpy(buffer, str, 1000); \ @@ -424,12 +439,13 @@ TEST(Reader, ParseArray_Error) { BaseReaderHandler<> h; \ GenericReader, UTF8<>, CrtAllocator> reader; \ EXPECT_FALSE(reader.Parse<0>(s, h)); \ + EXPECT_EQ(errorCode, reader.GetParseErrorCode());\ } - // Must be a comma or ']' after an array element. - TEST_ARRAY_ERROR("["); - TEST_ARRAY_ERROR("[}"); - TEST_ARRAY_ERROR("[1 2]"); + // Missing a comma or ']' after an array element. + TEST_ARRAY_ERROR(kParseErrorArrayMissCommaOrSquareBracket, "[1"); + TEST_ARRAY_ERROR(kParseErrorArrayMissCommaOrSquareBracket, "[1}"); + TEST_ARRAY_ERROR(kParseErrorArrayMissCommaOrSquareBracket, "[1 2]"); #undef TEST_ARRAY_ERROR } @@ -519,39 +535,7 @@ TEST(Reader, Parse_EmptyObject) { EXPECT_EQ(2u, h.step_); } -TEST(Reader, ParseObject_Error) { -#define TEST_OBJECT_ERROR(str) \ - { \ - char buffer[1001]; \ - strncpy(buffer, str, 1000); \ - InsituStringStream s(buffer); \ - BaseReaderHandler<> h; \ - GenericReader, UTF8<>, CrtAllocator> reader; \ - EXPECT_FALSE(reader.Parse<0>(s, h)); \ - } - - // Name of an object member must be a string - TEST_OBJECT_ERROR("{null:1}"); - TEST_OBJECT_ERROR("{true:1}"); - TEST_OBJECT_ERROR("{false:1}"); - TEST_OBJECT_ERROR("{1:1}"); - TEST_OBJECT_ERROR("{[]:1}"); - TEST_OBJECT_ERROR("{{}:1}"); - TEST_OBJECT_ERROR("{xyz:1}"); - - // There must be a colon after the name of object member - TEST_OBJECT_ERROR("{\"a\" 1}"); - TEST_OBJECT_ERROR("{\"a\",1}"); - - // Must be a comma or '}' after an object member - TEST_OBJECT_ERROR("{]"); - TEST_OBJECT_ERROR("{\"a\":1]"); - -#undef TEST_OBJECT_ERROR -} - -TEST(Reader, Parse_Error) { -#define TEST_ERROR(str) \ +#define TEST_ERROR(errorCode, str) \ { \ char buffer[1001]; \ strncpy(buffer, str, 1000); \ @@ -559,31 +543,57 @@ TEST(Reader, Parse_Error) { BaseReaderHandler<> h; \ Reader reader; \ EXPECT_FALSE(reader.Parse<0>(s, h)); \ + EXPECT_EQ(errorCode, reader.GetParseErrorCode());\ } - // Text only contains white space(s) - TEST_ERROR(""); - TEST_ERROR(" "); - TEST_ERROR(" \n"); +TEST(Reader, ParseDocument_Error) { + // The document is empty. + TEST_ERROR(kParseErrorDocumentEmpty, ""); + TEST_ERROR(kParseErrorDocumentEmpty, " "); + TEST_ERROR(kParseErrorDocumentEmpty, " \n"); - // Expect either an object or array at root - TEST_ERROR("null"); - TEST_ERROR("true"); - TEST_ERROR("false"); - TEST_ERROR("\"s\""); - TEST_ERROR("0"); + // The document root must be either object or array. + TEST_ERROR(kParseErrorDocumentRootNotObjectOrArray, "null"); + TEST_ERROR(kParseErrorDocumentRootNotObjectOrArray, "true"); + TEST_ERROR(kParseErrorDocumentRootNotObjectOrArray, "false"); + TEST_ERROR(kParseErrorDocumentRootNotObjectOrArray, "\"s\""); + TEST_ERROR(kParseErrorDocumentRootNotObjectOrArray, "0"); - // Nothing should follow the root object or array - TEST_ERROR("[] 0"); - TEST_ERROR("{} 0"); + // The document root must not follow by other values. + TEST_ERROR(kParseErrorDocumentRootNotSingular, "[] 0"); + TEST_ERROR(kParseErrorDocumentRootNotSingular, "{} 0"); +} - // Invalid value - TEST_ERROR("nulL"); - TEST_ERROR("truE"); - TEST_ERROR("falsE"); +TEST(Reader, ParseValue_Error) { + // Invalid value. + TEST_ERROR(kParseErrorValueInvalid, "[nulL]"); + TEST_ERROR(kParseErrorValueInvalid, "[truE]"); + TEST_ERROR(kParseErrorValueInvalid, "[falsE]"); + TEST_ERROR(kParseErrorValueInvalid, "[a]"); + TEST_ERROR(kParseErrorValueInvalid, "[.1]"); +} + +TEST(Reader, ParseObject_Error) { + // Missing a name for object member. + TEST_ERROR(kParseErrorObjectMissName, "{1}"); + TEST_ERROR(kParseErrorObjectMissName, "{:1}"); + TEST_ERROR(kParseErrorObjectMissName, "{null:1}"); + TEST_ERROR(kParseErrorObjectMissName, "{true:1}"); + TEST_ERROR(kParseErrorObjectMissName, "{false:1}"); + TEST_ERROR(kParseErrorObjectMissName, "{1:1}"); + TEST_ERROR(kParseErrorObjectMissName, "{[]:1}"); + TEST_ERROR(kParseErrorObjectMissName, "{{}:1}"); + TEST_ERROR(kParseErrorObjectMissName, "{xyz:1}"); + + // Missing a colon after a name of object member. + TEST_ERROR(kParseErrorObjectMissColon, "{\"a\" 1}"); + TEST_ERROR(kParseErrorObjectMissColon, "{\"a\",1}"); + + // Must be a comma or '}' after an object member + TEST_ERROR(kParseErrorObjectMissCommaOrCurlyBracket, "{\"a\":1]"); +} #undef TEST_ERROR -} TEST(Reader, SkipWhitespace) { StringStream ss(" A \t\tB\n \n\nC\r\r \rD \t\n\r E");