CharReader: Add StructuredError (#1409)

* CharReader: Add Structured Error

Add getStructuredError to CharReader

* run clang format

---------

Co-authored-by: Jordan Bayles <bayles.jordan@gmail.com>
Co-authored-by: Jordan Bayles <jophba@chromium.org>
diff --git a/include/json/reader.h b/include/json/reader.h
index 85539d1..38b9360 100644
--- a/include/json/reader.h
+++ b/include/json/reader.h
@@ -244,6 +244,12 @@
  */
 class JSON_API CharReader {
 public:
+  struct JSON_API StructuredError {
+    ptrdiff_t offset_start;
+    ptrdiff_t offset_limit;
+    String message;
+  };
+
   virtual ~CharReader() = default;
   /** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a>
    * document. The document must be a UTF-8 encoded string containing the
@@ -262,7 +268,12 @@
    * error occurred.
    */
   virtual bool parse(char const* beginDoc, char const* endDoc, Value* root,
-                     String* errs) = 0;
+                     String* errs);
+
+  /** \brief Returns a vector of structured errors encountered while parsing.
+   * Each parse call resets the stored list of errors.
+   */
+  std::vector<StructuredError> getStructuredErrors() const;
 
   class JSON_API Factory {
   public:
@@ -272,6 +283,20 @@
      */
     virtual CharReader* newCharReader() const = 0;
   }; // Factory
+
+protected:
+  class Impl {
+  public:
+    virtual ~Impl() = default;
+    virtual bool parse(char const* beginDoc, char const* endDoc, Value* root,
+                       String* errs) = 0;
+    virtual std::vector<StructuredError> getStructuredErrors() const = 0;
+  };
+
+  explicit CharReader(std::unique_ptr<Impl> impl) : _impl(std::move(impl)) {}
+
+private:
+  std::unique_ptr<Impl> _impl;
 }; // CharReader
 
 /** \brief Build a CharReader implementation.
diff --git a/src/lib_json/json_reader.cpp b/src/lib_json/json_reader.cpp
index 86ed030..4ab4dff 100644
--- a/src/lib_json/json_reader.cpp
+++ b/src/lib_json/json_reader.cpp
@@ -878,17 +878,12 @@
 public:
   using Char = char;
   using Location = const Char*;
-  struct StructuredError {
-    ptrdiff_t offset_start;
-    ptrdiff_t offset_limit;
-    String message;
-  };
 
   explicit OurReader(OurFeatures const& features);
   bool parse(const char* beginDoc, const char* endDoc, Value& root,
              bool collectComments = true);
   String getFormattedErrorMessages() const;
-  std::vector<StructuredError> getStructuredErrors() const;
+  std::vector<CharReader::StructuredError> getStructuredErrors() const;
 
 private:
   OurReader(OurReader const&);      // no impl
@@ -1836,10 +1831,11 @@
   return formattedMessage;
 }
 
-std::vector<OurReader::StructuredError> OurReader::getStructuredErrors() const {
-  std::vector<OurReader::StructuredError> allErrors;
+std::vector<CharReader::StructuredError>
+OurReader::getStructuredErrors() const {
+  std::vector<CharReader::StructuredError> allErrors;
   for (const auto& error : errors_) {
-    OurReader::StructuredError structured;
+    CharReader::StructuredError structured;
     structured.offset_start = error.token_.start_ - begin_;
     structured.offset_limit = error.token_.end_ - begin_;
     structured.message = error.message_;
@@ -1849,20 +1845,36 @@
 }
 
 class OurCharReader : public CharReader {
-  bool const collectComments_;
-  OurReader reader_;
 
 public:
   OurCharReader(bool collectComments, OurFeatures const& features)
-      : collectComments_(collectComments), reader_(features) {}
-  bool parse(char const* beginDoc, char const* endDoc, Value* root,
-             String* errs) override {
-    bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_);
-    if (errs) {
-      *errs = reader_.getFormattedErrorMessages();
+      : CharReader(
+            std::unique_ptr<OurImpl>(new OurImpl(collectComments, features))) {}
+
+protected:
+  class OurImpl : public Impl {
+  public:
+    OurImpl(bool collectComments, OurFeatures const& features)
+        : collectComments_(collectComments), reader_(features) {}
+
+    bool parse(char const* beginDoc, char const* endDoc, Value* root,
+               String* errs) override {
+      bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_);
+      if (errs) {
+        *errs = reader_.getFormattedErrorMessages();
+      }
+      return ok;
     }
-    return ok;
-  }
+
+    std::vector<CharReader::StructuredError>
+    getStructuredErrors() const override {
+      return reader_.getStructuredErrors();
+    }
+
+  private:
+    bool const collectComments_;
+    OurReader reader_;
+  };
 };
 
 CharReaderBuilder::CharReaderBuilder() { setDefaults(&settings_); }
@@ -1952,6 +1964,16 @@
   //! [CharReaderBuilderDefaults]
 }
 
+std::vector<CharReader::StructuredError>
+CharReader::getStructuredErrors() const {
+  return _impl->getStructuredErrors();
+}
+
+bool CharReader::parse(char const* beginDoc, char const* endDoc, Value* root,
+                       String* errs) {
+  return _impl->parse(beginDoc, endDoc, root, errs);
+}
+
 //////////////////////////////////
 // global functions
 
diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp
index 1ef33bb..fa41d19 100644
--- a/src/test_lib_json/main.cpp
+++ b/src/test_lib_json/main.cpp
@@ -3917,6 +3917,36 @@
                              example.size()));
 }
 
+struct ParseWithStructuredErrorsTest : JsonTest::TestCase {
+  void testErrors(
+      const std::string& doc, bool success,
+      const std::vector<Json::CharReader::StructuredError>& expectedErrors) {
+    Json::CharReaderBuilder b;
+    CharReaderPtr reader(b.newCharReader());
+    Json::Value root;
+    JSONTEST_ASSERT_EQUAL(
+        reader->parse(doc.data(), doc.data() + doc.length(), &root, nullptr),
+        success);
+    auto actualErrors = reader->getStructuredErrors();
+    JSONTEST_ASSERT_EQUAL(expectedErrors.size(), actualErrors.size());
+    for (std::size_t i = 0; i < actualErrors.size(); i++) {
+      const auto& a = actualErrors[i];
+      const auto& e = expectedErrors[i];
+      JSONTEST_ASSERT_EQUAL(a.offset_start, e.offset_start);
+      JSONTEST_ASSERT_EQUAL(a.offset_limit, e.offset_limit);
+      JSONTEST_ASSERT_STRING_EQUAL(a.message, e.message);
+    }
+  }
+};
+
+JSONTEST_FIXTURE_LOCAL(ParseWithStructuredErrorsTest, success) {
+  testErrors("{}", true, {});
+}
+
+JSONTEST_FIXTURE_LOCAL(ParseWithStructuredErrorsTest, singleError) {
+  testErrors("{ 1 : 2 }", false, {{2, 3, "Missing '}' or object member name"}});
+}
+
 int main(int argc, const char* argv[]) {
   JsonTest::Runner runner;