#pragma once #include #include #include #include #include namespace roro::release { // Reads JSON as it arrives, a chunk at a time, and reports each plain value (a string, a number, // true, false, null) with where it was found: "tag_name", "assets[1].name", "[2].draft". Nothing // is kept but the path, so a 33 KB list of releases costs a few hundred bytes. A value longer than // `maxValueBytes` is cut (never in the middle of a character) and reported as truncated. // Meant for a server's answer, not for validating JSON: it only refuses what it can't follow. class JsonScanner { public: using Sink = std::function; explicit JsonScanner(Sink sink, size_t maxValueBytes = 300) : sink_(std::move(sink)), max_(maxValueBytes) {} void feed(const char* data, size_t len); bool failed() const { return failed_; } bool done() const { return done_ && !failed_; } // the top-level value is complete private: enum class Expect : uint8_t { Value, KeyOrEnd, Colon, CommaOrEnd }; struct Frame { bool isObject; std::string key; int index; }; void step(char c); void startString(bool key); void stringChar(char c); void appendCodePoint(uint32_t cp); void endString(); void endLiteral(); void valueDone(); void append(const char* bytes, size_t n); std::string path() const; void fail() { failed_ = true; } Sink sink_; size_t max_; std::vector stack_; Expect expect_ = Expect::Value; bool inString_ = false, isKey_ = false, escape_ = false, inLiteral_ = false; int unicodeLeft_ = 0; uint32_t unicode_ = 0, highSurrogate_ = 0; bool truncated_ = false; std::string text_; bool failed_ = false, done_ = false; }; } // namespace roro::release