Initial attempt at parsing commit requests

This commit is contained in:
2025-08-14 12:21:08 -04:00
parent 9d4c08747c
commit 61ae8420a8
5 changed files with 1097 additions and 2 deletions

282
src/commit_request.hpp Normal file
View File

@@ -0,0 +1,282 @@
#pragma once
#include "arena_allocator.hpp"
#include <optional>
#include <stack>
#include <string>
#include <string_view>
#include <vector>
#include <weaseljson/weaseljson.h>
/**
* @brief Represents a precondition for a commit request.
*/
struct Precondition {
enum class Type { PointRead, RangeRead };
Type type;
std::optional<uint64_t> version;
std::string_view key;
std::optional<std::string_view> begin;
std::optional<std::string_view> end;
};
/**
* @brief Represents an operation in a commit request.
*/
struct Operation {
enum class Type { Write, Delete, RangeDelete };
Type type;
std::string_view key;
std::optional<std::string_view> value;
std::optional<std::string_view> begin;
std::optional<std::string_view> end;
};
/**
* @brief Represents a commit request as described in the API specification.
*
* All string data is stored in the arena allocator to ensure efficient
* memory management and ownership.
*/
class CommitRequest {
public:
// Parser state
enum class ParseState {
Root,
RequestId,
LeaderId,
ReadVersion,
PreconditionsArray,
PreconditionObject,
OperationsArray,
OperationObject
};
enum class ParseStatus {
Incomplete, // Still need more data
Complete, // Successfully parsed complete JSON
Error // Parse error occurred
};
struct ParserContext {
std::stack<ParseState> state_stack;
std::string current_key;
std::string current_string;
std::string current_number;
bool in_key = false;
bool parse_error = false;
bool parse_complete = false;
// Current objects being parsed
Precondition current_precondition{};
Operation current_operation{};
// Parsing state for nested structures
std::string precondition_type;
std::string operation_type;
};
private:
ArenaAllocator arena_;
std::optional<std::string_view> request_id_;
std::string_view leader_id_;
uint64_t read_version_ = 0;
std::vector<Precondition> preconditions_;
std::vector<Operation> operations_;
ParserContext parser_context_;
WeaselJsonParser *json_parser_ = nullptr;
public:
/**
* @brief Construct a new CommitRequest with the given initial arena size.
* @param arena_size Initial size for the arena allocator
*/
explicit CommitRequest(size_t arena_size = 4096) : arena_(arena_size) {}
/**
* @brief Destructor - cleans up any active parser.
*/
~CommitRequest() {
if (json_parser_) {
WeaselJsonParser_destroy(json_parser_);
}
}
// Move constructor
CommitRequest(CommitRequest &&other) noexcept
: arena_(std::move(other.arena_)), request_id_(other.request_id_),
leader_id_(other.leader_id_), read_version_(other.read_version_),
preconditions_(std::move(other.preconditions_)),
operations_(std::move(other.operations_)),
parser_context_(std::move(other.parser_context_)),
json_parser_(other.json_parser_) {
other.json_parser_ = nullptr;
}
// Move assignment operator
CommitRequest &operator=(CommitRequest &&other) noexcept {
if (this != &other) {
if (json_parser_) {
WeaselJsonParser_destroy(json_parser_);
}
arena_ = std::move(other.arena_);
request_id_ = other.request_id_;
leader_id_ = other.leader_id_;
read_version_ = other.read_version_;
preconditions_ = std::move(other.preconditions_);
operations_ = std::move(other.operations_);
parser_context_ = std::move(other.parser_context_);
json_parser_ = other.json_parser_;
other.json_parser_ = nullptr;
}
return *this;
}
// Copy constructor and assignment are deleted (not safe with parser state)
CommitRequest(const CommitRequest &) = delete;
CommitRequest &operator=(const CommitRequest &) = delete;
/**
* @brief Parse a JSON string into a CommitRequest object (one-shot parsing).
* @param json_str The JSON string to parse
* @return true if parsing succeeded, false otherwise
*/
bool parse_json(std::string_view json_str);
/**
* @brief Initialize streaming JSON parsing.
* @return true if initialization succeeded, false otherwise
*/
bool begin_streaming_parse();
/**
* @brief Parse additional JSON data incrementally.
* @param data Pointer to the data buffer
* @param len Length of the data
* @return ParseStatus indicating current parse state
*/
ParseStatus parse_chunk(char *data, size_t len);
/**
* @brief Finish streaming parse (call when no more data is available).
* @return ParseStatus indicating final parse result
*/
ParseStatus finish_streaming_parse();
/**
* @brief Check if parsing is complete and successful.
* @return true if parsing is complete and successful
*/
bool is_parse_complete() const {
return parser_context_.parse_complete && !parser_context_.parse_error;
}
/**
* @brief Check if there was a parse error.
* @return true if there was a parse error
*/
bool has_parse_error() const { return parser_context_.parse_error; }
/**
* @brief Get the request ID if present.
* @return Optional request ID
*/
const std::optional<std::string_view> &request_id() const {
return request_id_;
}
/**
* @brief Get the leader ID.
* @return Leader ID string view
*/
std::string_view leader_id() const { return leader_id_; }
/**
* @brief Get the read version.
* @return Read version number
*/
uint64_t read_version() const { return read_version_; }
/**
* @brief Get the preconditions.
* @return Vector of preconditions
*/
const std::vector<Precondition> &preconditions() const {
return preconditions_;
}
/**
* @brief Get the operations.
* @return Vector of operations
*/
const std::vector<Operation> &operations() const { return operations_; }
/**
* @brief Get the total allocated bytes in the arena.
* @return Total allocated bytes
*/
size_t total_allocated() const { return arena_.total_allocated(); }
/**
* @brief Get the used bytes in the arena.
* @return Used bytes
*/
size_t used_bytes() const { return arena_.used_bytes(); }
/**
* @brief Reset the commit request for reuse.
*/
void reset() {
arena_.reset();
request_id_.reset();
leader_id_ = {};
read_version_ = 0;
preconditions_.clear();
operations_.clear();
// Reset parser state
if (json_parser_) {
WeaselJsonParser_destroy(json_parser_);
json_parser_ = nullptr;
}
parser_context_ = ParserContext{};
parser_context_.state_stack.push(ParseState::Root);
}
// Weaseljson callbacks (public for global callbacks)
static void on_begin_object(void *userdata);
static void on_end_object(void *userdata);
static void on_string_data(void *userdata, const char *buf, int len,
int done);
static void on_key_data(void *userdata, const char *buf, int len, int done);
static void on_begin_array(void *userdata);
static void on_end_array(void *userdata);
static void on_number_data(void *userdata, const char *buf, int len,
int done);
static void on_true_literal(void *userdata);
static void on_false_literal(void *userdata);
static void on_null_literal(void *userdata);
private:
/**
* @brief Copy a string into the arena and return a string_view.
* @param str The string to copy
* @return String view pointing to arena-allocated memory
*/
std::string_view store_string(std::string_view str);
/**
* @brief Decode a base64 string and store it in the arena.
* @param base64_str The base64 encoded string
* @return String view of decoded data, or empty view if decoding failed
*/
std::string_view decode_base64(std::string_view base64_str);
void handle_completed_string();
void handle_completed_number();
void handle_completed_key();
};