diff --git a/serde_json/fast_serde_json_bridge.cc b/serde_json/fast_serde_json_bridge.cc new file mode 100644 index 0000000..8ddc0b5 --- /dev/null +++ b/serde_json/fast_serde_json_bridge.cc @@ -0,0 +1,463 @@ +#include "fast_serde_json_bridge.h" + +#include +#include +#include +#include +#include + +#include +#include "rust/fast_serde_json_rs.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" + +namespace security::json::fast_serde_json_bridge { + +namespace { + +std::string StringFromVec(const std::vector& bytes) { + if (bytes.empty()) return ""; + return std::string(reinterpret_cast(bytes.data()), bytes.size()); +} + +absl::Span ToBytesSpan(absl::string_view view) { + return absl::Span( + reinterpret_cast(view.data()), view.size()); +} + +} // namespace + +FastSerdeJson::FastSerdeJson(fast_serde_json_rs::FastSerdeJson sj) + : json_obj_(std::move(sj)) {} + +absl::StatusOr FastSerdeJson::CloneSubtree(NodeHandle handle) { + rs_std::Result + rs_result = json_obj_.clone_subtree(std::move(handle)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + return FastSerdeJson(std::move(rs_result).value()); +} + +FastSerdeJson FastSerdeJson::CreateObject() { + return FastSerdeJson(fast_serde_json_rs::FastSerdeJson::new_object()); +} + +FastSerdeJson FastSerdeJson::CreateArray() { + return FastSerdeJson(fast_serde_json_rs::FastSerdeJson::new_array()); +} + +FastSerdeJson FastSerdeJson::CreateInt(int64_t value) { + return FastSerdeJson(fast_serde_json_rs::FastSerdeJson::from_i64(value)); +} + +absl::StatusOr FastSerdeJson::CreateDouble(double value) { + rs_std::Result + rs_result = fast_serde_json_rs::FastSerdeJson::try_from_f64(value); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + return FastSerdeJson(std::move(rs_result).value()); +} + +FastSerdeJson FastSerdeJson::CreateBool(bool value) { + return FastSerdeJson(fast_serde_json_rs::FastSerdeJson::from_bool(value)); +} + +FastSerdeJson FastSerdeJson::CreateNull() { + return FastSerdeJson(fast_serde_json_rs::FastSerdeJson::new_null()); +} + +absl::StatusOr FastSerdeJson::CreateString( + absl::string_view value) { + rs_std::Result + rs_result = + fast_serde_json_rs::FastSerdeJson::try_from_utf8(ToBytesSpan(value)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + return FastSerdeJson(std::move(rs_result).value()); +} + +absl::StatusOr FastSerdeJson::Parse(absl::string_view data) { + rs_std::Result + rs_result = + fast_serde_json_rs::FastSerdeJson::try_parse(ToBytesSpan(data)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + return FastSerdeJson(std::move(rs_result).value()); +} + +absl::StatusOr FastSerdeJson::GetField( + absl::string_view key, NodeHandle handle) { + rs_std::Result + rs_result = json_obj_.get_field(ToBytesSpan(key), std::move(handle)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + + return std::move(rs_result).value(); +} + +absl::StatusOr FastSerdeJson::GetFieldObject( + absl::string_view key, NodeHandle handle) { + rs_std::Result + rs_result = + json_obj_.get_field_object(ToBytesSpan(key), std::move(handle)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + + return std::move(rs_result).value(); +} + +absl::StatusOr FastSerdeJson::GetBool(NodeHandle handle) { + rs_std::Result rs_result = + json_obj_.get_bool(std::move(handle)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + + return std::move(rs_result).value(); +} + +absl::StatusOr FastSerdeJson::GetString(NodeHandle handle) { + rs_std::Result, fast_serde_json_rs::Status> rs_result = + json_obj_.get_string(std::move(handle)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + + return StringFromVec(std::move(rs_result).value()); +} + +absl::StatusOr FastSerdeJson::GetInt(NodeHandle handle) { + rs_std::Result rs_result = + json_obj_.get_int(std::move(handle)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + + return std::move(rs_result).value(); +} + +absl::StatusOr FastSerdeJson::GetDouble(NodeHandle handle) { + rs_std::Result rs_result = + json_obj_.get_double(std::move(handle)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + + return std::move(rs_result).value(); +} + +absl::StatusOr> FastSerdeJson::GetArray( + NodeHandle handle) { + rs_std::Result, + fast_serde_json_rs::Status> + rs_result = json_obj_.get_array(std::move(handle)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + + return std::move(rs_result).value(); +} + +absl::StatusOr FastSerdeJson::GetArrayElement( + size_t index, NodeHandle handle) { + rs_std::Result + rs_result = json_obj_.get_array_element(index, std::move(handle)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + + return std::move(rs_result).value(); +} + +absl::StatusOr FastSerdeJson::GetFieldString(absl::string_view key, + NodeHandle handle) { + rs_std::Result, fast_serde_json_rs::Status> rs_result = + json_obj_.get_field_string(ToBytesSpan(key), std::move(handle)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + + return StringFromVec(std::move(rs_result).value()); +} + +absl::StatusOr FastSerdeJson::GetFieldBool(absl::string_view key, + NodeHandle handle) { + rs_std::Result rs_result = + json_obj_.get_field_bool(ToBytesSpan(key), std::move(handle)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + + return std::move(rs_result).value(); +} + +absl::StatusOr FastSerdeJson::GetFieldInt(absl::string_view key, + NodeHandle handle) { + rs_std::Result rs_result = + json_obj_.get_field_int(ToBytesSpan(key), std::move(handle)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + + return std::move(rs_result).value(); +} + +absl::StatusOr FastSerdeJson::GetFieldDouble(absl::string_view key, + NodeHandle handle) { + rs_std::Result rs_result = + json_obj_.get_field_double(ToBytesSpan(key), std::move(handle)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + + return std::move(rs_result).value(); +} + +absl::StatusOr> +FastSerdeJson::GetFieldArray(absl::string_view key, NodeHandle handle) { + rs_std::Result, + fast_serde_json_rs::Status> + rs_result = + json_obj_.get_field_array(ToBytesSpan(key), std::move(handle)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + + return std::move(rs_result).value(); +} + +absl::StatusOr FastSerdeJson::GetFieldArrayElement( + absl::string_view key, size_t index, NodeHandle handle) { + rs_std::Result + rs_result = json_obj_.get_field_array_element(ToBytesSpan(key), index, + std::move(handle)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + + return std::move(rs_result).value(); +} + +bool FastSerdeJson::IsNull(NodeHandle handle) { + return json_obj_.is_null(std::move(handle)); +} + +absl::StatusOr FastSerdeJson::IsEmpty(NodeHandle handle) { + rs_std::Result rs_result = + json_obj_.is_empty(std::move(handle)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + + return std::move(rs_result).value(); +} + +bool FastSerdeJson::IsObject(NodeHandle handle) { + return json_obj_.is_object(std::move(handle)); +} + +bool FastSerdeJson::IsArray(NodeHandle handle) { + return json_obj_.is_array(std::move(handle)); +} + +bool FastSerdeJson::IsString(NodeHandle handle) { + return json_obj_.is_string(std::move(handle)); +} + +bool FastSerdeJson::IsNumber(NodeHandle handle) { + return json_obj_.is_number(std::move(handle)); +} + +bool FastSerdeJson::IsInt(NodeHandle handle) { + return json_obj_.is_i64(std::move(handle)); +} + +bool FastSerdeJson::IsDouble(NodeHandle handle) { + return json_obj_.is_f64(std::move(handle)); +} + +bool FastSerdeJson::IsBool(NodeHandle handle) { + return json_obj_.is_boolean(std::move(handle)); +} + +std::string FastSerdeJson::ToString(bool sort_keys, NodeHandle handle) { + return StringFromVec(json_obj_.to_string(sort_keys, std::move(handle))); +} + +absl::StatusOr> FastSerdeJson::GetKeys( + NodeHandle handle) { + rs_std::Result>, fast_serde_json_rs::Status> + rs_result = json_obj_.keys(std::move(handle)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + + const std::vector>& rust_raw_strings = rs_result.value(); + std::vector keys; + keys.reserve(rust_raw_strings.size()); + for (const std::vector& raw : rust_raw_strings) { + keys.push_back(StringFromVec(raw)); + } + return keys; +} + +absl::StatusOr FastSerdeJson::HasField(absl::string_view key, + NodeHandle handle) { + rs_std::Result rs_result = + json_obj_.has_field(ToBytesSpan(key), std::move(handle)); + + if (!rs_result.has_value()) { + return rs_result.err().status(); + } + + return std::move(rs_result).value(); +} + +absl::StatusOr FastSerdeJson::ToProtoStruct( + NodeHandle handle) { + google::protobuf::Struct result; + absl::StatusOr> keys = GetKeys(handle); + if (!keys.ok()) return keys.status(); + for (std::string& key : *keys) { + absl::StatusOr field_handle = GetField(key, handle); + if (!field_handle.ok()) return field_handle.status(); + absl::StatusOr value = ToProtoValue(*field_handle); + if (!value.ok()) return value.status(); + result.mutable_fields()->insert({std::move(key), std::move(*value)}); + } + return result; +} + +absl::StatusOr FastSerdeJson::ToProtoValue( + NodeHandle handle) { + google::protobuf::Value result; + + if (IsObject(handle)) { + absl::StatusOr proto_struct = + ToProtoStruct(handle); + if (!proto_struct.ok()) return proto_struct.status(); + *result.mutable_struct_value() = std::move(*proto_struct); + } else if (IsString(handle)) { + absl::StatusOr str = GetString(handle); + if (!str.ok()) return str.status(); + result.set_string_value(std::move(*str)); + } else if (IsInt(handle)) { + absl::StatusOr int_val = GetInt(handle); + if (!int_val.ok()) return int_val.status(); + result.set_number_value(*int_val); + } else if (IsDouble(handle)) { + absl::StatusOr double_val = GetDouble(handle); + if (!double_val.ok()) return double_val.status(); + result.set_number_value(*double_val); + } else if (IsBool(handle)) { + absl::StatusOr bool_val = GetBool(handle); + if (!bool_val.ok()) return bool_val.status(); + result.set_bool_value(*bool_val); + } else if (IsArray(handle)) { + google::protobuf::ListValue* list_value = result.mutable_list_value(); + absl::StatusOr> array = GetArray(handle); + if (!array.ok()) return array.status(); + for (const NodeHandle& element_handle : *array) { + absl::StatusOr val = + ToProtoValue(element_handle); + if (!val.ok()) return val.status(); + *list_value->add_values() = std::move(*val); + } + } else if (IsNull(handle)) { + result.set_null_value(google::protobuf::NullValue::NULL_VALUE); + } else { + return absl::FailedPreconditionError( + absl::StrCat("Unexpected type in the object: ", + ToString(/*sort_keys=*/true, handle))); + } + + return result; +} + +absl::Status FastSerdeJson::AddFieldInt(absl::string_view key, int64_t value, + NodeHandle handle) { + return json_obj_.add_field_int(ToBytesSpan(key), value, std::move(handle)) + .status(); +} + +absl::Status FastSerdeJson::AddFieldBool(absl::string_view key, bool value, + NodeHandle handle) { + return json_obj_.add_field_bool(ToBytesSpan(key), value, std::move(handle)) + .status(); +} + +absl::Status FastSerdeJson::AddFieldString(absl::string_view key, + const absl::string_view value, + NodeHandle handle) { + return json_obj_ + .add_field_string(ToBytesSpan(key), ToBytesSpan(value), std::move(handle)) + .status(); +} + +absl::Status FastSerdeJson::AddFieldDouble(absl::string_view key, double value, + NodeHandle handle) { + return json_obj_.add_field_double(ToBytesSpan(key), value, std::move(handle)) + .status(); +} + +absl::Status FastSerdeJson::AddFieldNull(absl::string_view key, + NodeHandle handle) { + return json_obj_.add_field_null(ToBytesSpan(key), std::move(handle)).status(); +} + +absl::Status FastSerdeJson::AddFieldObject(absl::string_view key, + FastSerdeJson value, + NodeHandle handle) { + return json_obj_ + .add_field_object(ToBytesSpan(key), std::move(value.json_obj_), + std::move(handle)) + .status(); +} + +absl::Status FastSerdeJson::AddFieldArray(absl::string_view key, + std::vector value, + NodeHandle handle) { + std::vector arr; + arr.reserve(value.size()); + for (FastSerdeJson& v : value) { + arr.push_back(std::move(v.json_obj_)); + } + return json_obj_ + .add_field_array(ToBytesSpan(key), std::move(arr), std::move(handle)) + .status(); +} + +bool FastSerdeJson::operator==(const FastSerdeJson& other) const { + return json_obj_.is_json_equal(other.json_obj_); +} + +} // namespace security::json::fast_serde_json_bridge diff --git a/serde_json/fast_serde_json_bridge.h b/serde_json/fast_serde_json_bridge.h new file mode 100644 index 0000000..94203d7 --- /dev/null +++ b/serde_json/fast_serde_json_bridge.h @@ -0,0 +1,142 @@ +#ifndef SECURITY_JSON_SERDE_JSON_FAST_SERDE_JSON_BRIDGE_H_ +#define SECURITY_JSON_SERDE_JSON_FAST_SERDE_JSON_BRIDGE_H_ + +#include +#include +#include +#include +#include + +#include +#include "rust/fast_serde_json_rs.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" + +namespace security::json::fast_serde_json_bridge { + +// A high-performance, handle-based JSON document parser backed by Rust's +// serde_json. +// +// THREAD-SAFETY NOTICE: FastSerdeJson is NOT thread-safe for concurrent reads +// or writes. Under the hood, handle resolution utilizes an internal pointer +// cache inside a HandleRegistry. Rather than paying the runtime overhead of +// wrapping this cache in a mutex or atomics—which would force all consumers to +// pay a performance penalty even in single-threaded workflows—getter and +// navigation accessors are deliberately marked non-const. This makes it +// explicit to C++ consumers that internal state (the pointer cache) is modified +// during read operations, serving as a clear signal that external +// synchronization (such as a mutex) is required if an instance is shared across +// multiple threads. +class FastSerdeJson final { + public: + // Opaque handle referencing a specific node within a FastSerdeJson document. + // Handles are cheap to copy or move, and can be reused across document + // clones. A handle remains valid as long as its corresponding node in the + // document exists. + using NodeHandle = fast_serde_json_rs::NodeHandle; + + FastSerdeJson(FastSerdeJson&&) = default; + FastSerdeJson& operator=(FastSerdeJson&&) = default; + + // Move-only (not copyable). Explicit CloneSubtree method for copying. + FastSerdeJson(const FastSerdeJson&) = delete; + FastSerdeJson& operator=(const FastSerdeJson&) = delete; + + // Explicit clone of the document or a subtree specified by handle. + absl::StatusOr CloneSubtree(NodeHandle handle = {}); + + bool operator==(const FastSerdeJson& other) const; + + // Parses a raw JSON string into a FastSerdeJson object. + static absl::StatusOr Parse(absl::string_view data); + + // Creates a new FastSerdeJson of a given type. + static FastSerdeJson CreateObject(); + static FastSerdeJson CreateArray(); + static FastSerdeJson CreateInt(int64_t value); + static FastSerdeJson CreateBool(bool value); + static absl::StatusOr CreateDouble(double value); + static FastSerdeJson CreateNull(); + static absl::StatusOr CreateString(absl::string_view value); + + // Returns the value of the current json node (or root if handle is default). + absl::StatusOr GetInt(NodeHandle handle = {}); + absl::StatusOr GetBool(NodeHandle handle = {}); + absl::StatusOr GetString(NodeHandle handle = {}); + absl::StatusOr GetDouble(NodeHandle handle = {}); + absl::StatusOr> GetArray(NodeHandle handle = {}); + absl::StatusOr GetArrayElement(size_t index, + NodeHandle handle = {}); + + // Returns a handle to the corresponding `key` field of this json object. + absl::StatusOr GetField(absl::string_view key, + NodeHandle handle = {}); + + // Returns the value of the corresponding field of this json object. + absl::StatusOr GetFieldString(absl::string_view key, + NodeHandle handle = {}); + absl::StatusOr GetFieldBool(absl::string_view key, + NodeHandle handle = {}); + absl::StatusOr GetFieldInt(absl::string_view key, + NodeHandle handle = {}); + absl::StatusOr GetFieldDouble(absl::string_view key, + NodeHandle handle = {}); + absl::StatusOr GetFieldObject(absl::string_view key, + NodeHandle handle = {}); + absl::StatusOr> GetFieldArray(absl::string_view key, + NodeHandle handle = {}); + absl::StatusOr GetFieldArrayElement(absl::string_view key, + size_t index, + NodeHandle handle = {}); + + // Methods for checking the type of a json node. + // Note: IsEmpty returns an error status if `handle` is invalid. + // All other predicate methods (IsNull, IsObject, IsArray, etc.) return + // `false` if `handle` is invalid or refers to a non-existent path. + bool IsNull(NodeHandle handle = {}); + absl::StatusOr IsEmpty(NodeHandle handle = {}); + bool IsObject(NodeHandle handle = {}); + bool IsArray(NodeHandle handle = {}); + bool IsString(NodeHandle handle = {}); + bool IsNumber(NodeHandle handle = {}); + bool IsDouble(NodeHandle handle = {}); + bool IsBool(NodeHandle handle = {}); + bool IsInt(NodeHandle handle = {}); + + // If the target node is an object, returns whether the field exists. + absl::StatusOr HasField(absl::string_view key, NodeHandle handle = {}); + + // Returns the keys of a json object. + absl::StatusOr> GetKeys(NodeHandle handle = {}); + + std::string ToString(bool sort_keys = true, NodeHandle handle = {}); + absl::StatusOr ToProtoStruct( + NodeHandle handle = {}); + absl::StatusOr ToProtoValue(NodeHandle handle = {}); + + // Methods for adding fields to a JSON object node. + absl::Status AddFieldBool(absl::string_view key, bool value, + NodeHandle handle = {}); + absl::Status AddFieldDouble(absl::string_view key, double value, + NodeHandle handle = {}); + absl::Status AddFieldInt(absl::string_view key, int64_t value, + NodeHandle handle = {}); + absl::Status AddFieldNull(absl::string_view key, NodeHandle handle = {}); + absl::Status AddFieldObject(absl::string_view key, FastSerdeJson value, + NodeHandle handle = {}); + absl::Status AddFieldString(absl::string_view key, absl::string_view value, + NodeHandle handle = {}); + absl::Status AddFieldArray(absl::string_view key, + std::vector value, + NodeHandle handle = {}); + + private: + explicit FastSerdeJson(fast_serde_json_rs::FastSerdeJson sj); + + fast_serde_json_rs::FastSerdeJson json_obj_; +}; + +} // namespace security::json::fast_serde_json_bridge + +#endif // SECURITY_JSON_SERDE_JSON_FAST_SERDE_JSON_BRIDGE_H_ diff --git a/serde_json/fast_serde_json_bridge_test.cc b/serde_json/fast_serde_json_bridge_test.cc new file mode 100644 index 0000000..304c224 --- /dev/null +++ b/serde_json/fast_serde_json_bridge_test.cc @@ -0,0 +1,1098 @@ +#include "fast_serde_json_bridge.h" + +#include +#include +#include +#include +#include + +#include +#include "net/proto2/contrib/parse_proto/parse_text_proto.h" +#include "testing/base/public/gmock.h" +#include "testing/base/public/gunit.h" +#include "absl/status/status.h" +#include "absl/status/status_matchers.h" +#include "absl/strings/string_view.h" +#include "third_party/json/include/nlohmann/json.hpp" + +namespace { + +using ::absl_testing::StatusIs; +using ::proto2::contrib::parse_proto::ParseTextProtoOrDie; +using ::security::json::fast_serde_json_bridge::FastSerdeJson; +using ::testing::ElementsAre; +using ::testing::EqualsProto; +using ::testing::status::IsOkAndHolds; + +TEST(FastSerdeJsonBridge, SimpleParse) { + constexpr absl::string_view kJsonString = + "{\n" + " \"firstName\": \"John\",\n" + " \"lastName\": \"Doe\"\n" + "}"; + + EXPECT_OK(FastSerdeJson::Parse(kJsonString)); +} + +TEST(FastSerdeJsonBridge, FailParse) { + constexpr absl::string_view kInvalidJsonString = + "{\n" + " \"firstName\": \"John\",\n" + "}"; + + EXPECT_THAT(FastSerdeJson::Parse(kInvalidJsonString), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(FastSerdeJsonBridge, CheckFieldGetter) { + constexpr absl::string_view kJsonString = + "{\n" + " \"firstName\": \"John\",\n" + " \"lastName\": \"Doe\"\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_OK(json.GetField("firstName")); + EXPECT_OK(json.GetField("lastName")); + EXPECT_THAT(json.GetField("phone"), + StatusIs(absl::StatusCode::kFailedPrecondition)); +} + +TEST(FastSerdeJsonBridge, CheckGetBool) { + constexpr absl::string_view kJsonString = "true"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.GetBool(), IsOkAndHolds(true)); +} + +TEST(FastSerdeJsonBridge, CheckGetString) { + constexpr absl::string_view kJsonString = "\"string\""; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.GetString(), IsOkAndHolds("string")); +} + +TEST(FastSerdeJsonBridge, CheckGetInt) { + constexpr absl::string_view kJsonString = "1234"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.GetInt(), IsOkAndHolds(1234)); +} + +TEST(FastSerdeJsonBridge, CheckGetDouble) { + constexpr absl::string_view kJsonString = "1337.1234"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.GetDouble(), IsOkAndHolds(1337.1234)); +} + +TEST(FastSerdeJsonBridge, CheckGetArray) { + constexpr absl::string_view kJsonString = + "[\n" + " \"first\",\n" + " 2,\n" + " \"third\",\n" + " 4\n" + "]"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + ASSERT_OK_AND_ASSIGN(std::vector array, + json.GetArray()); + ASSERT_EQ(array.size(), 4); + + EXPECT_THAT(json.GetString(array[0]), IsOkAndHolds("first")); + EXPECT_THAT(json.GetInt(array[1]), IsOkAndHolds(2)); + EXPECT_THAT(json.GetString(array[2]), IsOkAndHolds("third")); + EXPECT_THAT(json.GetInt(array[3]), IsOkAndHolds(4)); +} + +TEST(FastSerdeJsonBridge, CheckGetArrayNotFromArray) { + constexpr absl::string_view kJsonString = "\"first\""; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.GetArray(), StatusIs(absl::StatusCode::kFailedPrecondition)); +} + +TEST(FastSerdeJsonBridge, CheckGetArrayElement) { + constexpr absl::string_view kJsonString = + "[\n" + " \"first\",\n" + " 2,\n" + " \"third\",\n" + " 4\n" + "]"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle elem0, + json.GetArrayElement(0)); + EXPECT_THAT(json.GetString(elem0), IsOkAndHolds("first")); + + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle elem1, + json.GetArrayElement(1)); + EXPECT_THAT(json.GetInt(elem1), IsOkAndHolds(2)); + + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle elem2, + json.GetArrayElement(2)); + EXPECT_THAT(json.GetString(elem2), IsOkAndHolds("third")); + + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle elem3, + json.GetArrayElement(3)); + EXPECT_THAT(json.GetInt(elem3), IsOkAndHolds(4)); +} + +TEST(FastSerdeJsonBridge, CheckGetArrayElementOutOfBounds) { + constexpr absl::string_view kJsonString = + "[\n" + " \"first\",\n" + " 2\n" + "]"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.GetArrayElement(2), StatusIs(absl::StatusCode::kOutOfRange)); +} + +TEST(FastSerdeJsonBridge, CheckGetArrayElementNotFromArray) { + constexpr absl::string_view kJsonString = "\"first\""; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.GetArrayElement(0), + StatusIs(absl::StatusCode::kFailedPrecondition)); +} + +TEST(FastSerdeJsonBridge, GetFieldString) { + constexpr absl::string_view kJsonString = + "{\n" + " \"firstName\": \"John\",\n" + " \"lastName\": \"Doe\"\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.GetFieldString("firstName"), IsOkAndHolds("John")); + EXPECT_THAT(json.GetFieldString("lastName"), IsOkAndHolds("Doe")); +} + +TEST(FastSerdeJsonBridge, GetFieldBool) { + constexpr absl::string_view kJsonString = + "{\n" + " \"value1\": true,\n" + " \"value2\": false\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.GetFieldBool("value1"), IsOkAndHolds(true)); + EXPECT_THAT(json.GetFieldBool("value2"), IsOkAndHolds(false)); +} + +TEST(FastSerdeJsonBridge, GetFieldInt) { + constexpr absl::string_view kJsonString = + "{\n" + " \"value1\": 123,\n" + " \"value2\": -444\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.GetFieldInt("value1"), IsOkAndHolds(123)); + EXPECT_THAT(json.GetFieldInt("value2"), IsOkAndHolds(-444)); +} + +TEST(FastSerdeJsonBridge, GetFieldDouble) { + constexpr absl::string_view kJsonString = + "{\n" + " \"value1\": 1.0,\n" + " \"value2\": 3.0\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.GetFieldDouble("value1"), IsOkAndHolds(1.0)); + EXPECT_THAT(json.GetFieldDouble("value2"), IsOkAndHolds(3.0)); +} + +TEST(FastSerdeJsonBridge, GetFieldObject) { + constexpr absl::string_view kJsonString = + "{\n" + " \"obj\": {" + " \"value1\": 1.0\n" + " }\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle obj_handle, + json.GetFieldObject("obj")); + EXPECT_THAT(json.GetFieldDouble("value1", obj_handle), IsOkAndHolds(1.0)); +} + +TEST(FastSerdeJsonBridge, GetFieldArray) { + constexpr absl::string_view kJsonString = + "{\n" + " \"value\": [\n" + " \"first\",\n" + " 2,\n" + " \"third\",\n" + " 4\n" + " ]\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + ASSERT_OK_AND_ASSIGN(std::vector array, + json.GetFieldArray("value")); + ASSERT_EQ(array.size(), 4); + + EXPECT_THAT(json.GetString(array[0]), IsOkAndHolds("first")); + EXPECT_THAT(json.GetInt(array[1]), IsOkAndHolds(2)); + EXPECT_THAT(json.GetString(array[2]), IsOkAndHolds("third")); + EXPECT_THAT(json.GetInt(array[3]), IsOkAndHolds(4)); +} + +TEST(FastSerdeJsonBridge, GetFieldArrayNotFromArray) { + constexpr absl::string_view kJsonString = + "{\n" + " \"value\": 1.0\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.GetFieldArray("value"), + StatusIs(absl::StatusCode::kFailedPrecondition)); +} + +TEST(FastSerdeJsonBridge, GetFieldArrayElement) { + constexpr absl::string_view kJsonString = + "{\n" + " \"value\": [\n" + " \"first\",\n" + " 2,\n" + " \"third\",\n" + " 4\n" + " ]\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle elem0, + json.GetFieldArrayElement("value", 0)); + EXPECT_THAT(json.GetString(elem0), IsOkAndHolds("first")); + + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle elem1, + json.GetFieldArrayElement("value", 1)); + EXPECT_THAT(json.GetInt(elem1), IsOkAndHolds(2)); + + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle elem2, + json.GetFieldArrayElement("value", 2)); + EXPECT_THAT(json.GetString(elem2), IsOkAndHolds("third")); + + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle elem3, + json.GetFieldArrayElement("value", 3)); + EXPECT_THAT(json.GetInt(elem3), IsOkAndHolds(4)); +} + +TEST(FastSerdeJsonBridge, GetFieldArrayElementOutOfBounds) { + constexpr absl::string_view kJsonString = + "{\n" + " \"value\": [\n" + " \"first\",\n" + " 2\n" + " ]\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.GetFieldArrayElement("value", 2), + StatusIs(absl::StatusCode::kOutOfRange)); +} + +TEST(FastSerdeJsonBridge, GetFieldArrayElementNotFromArray) { + constexpr absl::string_view kJsonString = + "{\n" + " \"value\": 1.0\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.GetFieldArrayElement("value", 0), + StatusIs(absl::StatusCode::kFailedPrecondition)); +} + +TEST(FastSerdeJsonBridge, GetFieldNotFromObject) { + constexpr absl::string_view kJsonString = "1337.1234"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.GetFieldString("test"), + StatusIs(absl::StatusCode::kFailedPrecondition)); + EXPECT_THAT(json.GetFieldBool("test"), + StatusIs(absl::StatusCode::kFailedPrecondition)); + EXPECT_THAT(json.GetFieldDouble("test"), + StatusIs(absl::StatusCode::kFailedPrecondition)); + EXPECT_THAT(json.GetFieldInt("test"), + StatusIs(absl::StatusCode::kFailedPrecondition)); + EXPECT_THAT(json.GetFieldObject("test"), + StatusIs(absl::StatusCode::kFailedPrecondition)); + EXPECT_THAT(json.GetFieldArray("test"), + StatusIs(absl::StatusCode::kFailedPrecondition)); + EXPECT_THAT(json.GetDouble(), IsOkAndHolds(1337.1234)); +} + +TEST(FastSerdeJsonBridge, IsNull) { + constexpr absl::string_view kJsonString = + "{\n" + " \"value\": null\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle value, json.GetField("value")); + + EXPECT_TRUE(json.IsNull(value)); + EXPECT_FALSE(json.IsObject(value)); + EXPECT_FALSE(json.IsArray(value)); + EXPECT_FALSE(json.IsString(value)); + EXPECT_FALSE(json.IsNumber(value)); + EXPECT_FALSE(json.IsBool(value)); + EXPECT_FALSE(json.IsDouble(value)); + EXPECT_FALSE(json.IsInt(value)); +} + +TEST(FastSerdeJsonBridge, IsEmpty) { + { + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse("null")); + EXPECT_THAT(json.IsEmpty(), IsOkAndHolds(true)) + << "Actual JSON: " << json.ToString(); + } + { + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse("{}")); + EXPECT_THAT(json.IsEmpty(), IsOkAndHolds(true)) + << "Actual JSON: " << json.ToString(); + } + { + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, + FastSerdeJson::Parse("{\"a\": 1}")); + EXPECT_THAT(json.IsEmpty(), IsOkAndHolds(false)); + } + { + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse("[]")); + EXPECT_THAT(json.IsEmpty(), IsOkAndHolds(true)) + << "Actual JSON: " << json.ToString(); + } + { + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse("[1]")); + EXPECT_THAT(json.IsEmpty(), IsOkAndHolds(false)); + } + { + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse("\"\"")); + EXPECT_THAT(json.IsEmpty(), IsOkAndHolds(false)); + } + { + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse("\"abc\"")); + EXPECT_THAT(json.IsEmpty(), IsOkAndHolds(false)); + } + { + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse("0")); + EXPECT_THAT(json.IsEmpty(), IsOkAndHolds(false)); + } + { + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse("true")); + EXPECT_THAT(json.IsEmpty(), IsOkAndHolds(false)); + } +} + +TEST(FastSerdeJsonBridge, IsObject) { + constexpr absl::string_view kJsonString = + "{\n" + " \"value\": {}\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle value, json.GetField("value")); + + EXPECT_TRUE(json.IsObject(value)); + EXPECT_FALSE(json.IsNull(value)); + EXPECT_FALSE(json.IsArray(value)); + EXPECT_FALSE(json.IsString(value)); + EXPECT_FALSE(json.IsNumber(value)); + EXPECT_FALSE(json.IsBool(value)); + EXPECT_FALSE(json.IsDouble(value)); + EXPECT_FALSE(json.IsInt(value)); +} + +TEST(FastSerdeJsonBridge, IsArray) { + constexpr absl::string_view kJsonString = + "{\n" + " \"value\": []\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle value, json.GetField("value")); + + EXPECT_TRUE(json.IsArray(value)); + EXPECT_FALSE(json.IsNull(value)); + EXPECT_FALSE(json.IsObject(value)); + EXPECT_FALSE(json.IsString(value)); + EXPECT_FALSE(json.IsNumber(value)); + EXPECT_FALSE(json.IsBool(value)); + EXPECT_FALSE(json.IsDouble(value)); + EXPECT_FALSE(json.IsInt(value)); +} + +TEST(FastSerdeJsonBridge, IsString) { + constexpr absl::string_view kJsonString = + "{\n" + " \"value\": \"\"\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle value, json.GetField("value")); + + EXPECT_TRUE(json.IsString(value)); + EXPECT_FALSE(json.IsNull(value)); + EXPECT_FALSE(json.IsObject(value)); + EXPECT_FALSE(json.IsArray(value)); + EXPECT_FALSE(json.IsNumber(value)); + EXPECT_FALSE(json.IsBool(value)); + EXPECT_FALSE(json.IsDouble(value)); + EXPECT_FALSE(json.IsInt(value)); +} + +TEST(FastSerdeJsonBridge, IsNumber) { + constexpr absl::string_view kJsonString = + "{\n" + " \"value\": 0\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle value, json.GetField("value")); + + EXPECT_TRUE(json.IsNumber(value)); + EXPECT_TRUE(json.IsInt(value)); + EXPECT_FALSE(json.IsNull(value)); + EXPECT_FALSE(json.IsObject(value)); + EXPECT_FALSE(json.IsArray(value)); + EXPECT_FALSE(json.IsString(value)); + EXPECT_FALSE(json.IsBool(value)); + EXPECT_FALSE(json.IsDouble(value)); +} + +TEST(FastSerdeJsonBridge, IsBool) { + constexpr absl::string_view kJsonString = + "{\n" + " \"value\": true\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle value, json.GetField("value")); + + EXPECT_TRUE(json.IsBool(value)); + EXPECT_FALSE(json.IsNull(value)); + EXPECT_FALSE(json.IsObject(value)); + EXPECT_FALSE(json.IsArray(value)); + EXPECT_FALSE(json.IsString(value)); + EXPECT_FALSE(json.IsNumber(value)); + EXPECT_FALSE(json.IsDouble(value)); + EXPECT_FALSE(json.IsInt(value)); +} + +TEST(FastSerdeJsonBridge, IsDouble) { + constexpr absl::string_view kJsonString = + "{\n" + " \"value\": 10.0\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle value, json.GetField("value")); + + EXPECT_TRUE(json.IsNumber(value)); + EXPECT_TRUE(json.IsDouble(value)); + EXPECT_FALSE(json.IsNull(value)); + EXPECT_FALSE(json.IsObject(value)); + EXPECT_FALSE(json.IsArray(value)); + EXPECT_FALSE(json.IsString(value)); + EXPECT_FALSE(json.IsBool(value)); + EXPECT_FALSE(json.IsInt(value)); +} + +TEST(FastSerdeJsonBridge, IsInt) { + constexpr absl::string_view kJsonString = + "{\n" + " \"value\": 10\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle value, json.GetField("value")); + + EXPECT_TRUE(json.IsNumber(value)); + EXPECT_TRUE(json.IsInt(value)); + EXPECT_FALSE(json.IsNull(value)); + EXPECT_FALSE(json.IsObject(value)); + EXPECT_FALSE(json.IsArray(value)); + EXPECT_FALSE(json.IsString(value)); + EXPECT_FALSE(json.IsBool(value)); + EXPECT_FALSE(json.IsDouble(value)); +} + +TEST(FastSerdeJsonBridge, GetKeys) { + constexpr absl::string_view kJsonString = + "{\n" + " \"firstName\": \"John\",\n" + " \"lastName\": \"Doe\",\n" + " \"value1\": 1.0,\n" + " \"value2\": 3.0\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.GetKeys(), IsOkAndHolds(ElementsAre("firstName", "lastName", + "value1", "value2"))); +} + +TEST(FastSerdeJsonBridge, GetKeysPreservesInsertionOrder) { + constexpr absl::string_view kJsonString = + "{\n" + " \"z\": 1,\n" + " \"a\": 2,\n" + " \"m\": 3\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.GetKeys(), IsOkAndHolds(ElementsAre("z", "a", "m"))); +} + +TEST(FastSerdeJsonBridge, GetKeysNotFromObject) { + constexpr absl::string_view kJsonString = "10"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.GetKeys(), StatusIs(absl::StatusCode::kFailedPrecondition)); +} + +TEST(FastSerdeJsonBridge, ToString) { + constexpr absl::string_view kJsonString = + "{\n" + " \"firstName\": \"John\",\n" + " \"lastName\": \"Doe\",\n" + " \"value1\": 1.0,\n" + " \"value2\": 3.0\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_EQ(json.ToString(), + "{\"firstName\":\"John\",\"lastName\":\"Doe\",\"value1\":1.0," + "\"value2\":3.0}"); +} + +TEST(FastSerdeJsonBridge, ToStringWithSorting) { + constexpr absl::string_view kJsonString = + "{\n" + " \"z\": 1,\n" + " \"a\": 2\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + + // Default is sorted. + EXPECT_EQ(json.ToString(), "{\"a\":2,\"z\":1}"); + EXPECT_EQ(json.ToString(/*sort_keys=*/true), "{\"a\":2,\"z\":1}"); + + // Can opt-out of sorting. + EXPECT_EQ(json.ToString(/*sort_keys=*/false), "{\"z\":1,\"a\":2}"); +} + +TEST(FastSerdeJsonBridge, ToStringWithNestedObjects) { + constexpr absl::string_view kJsonString = + "{\n" + " \"attributes\": [\n" + " {\n" + " \"zzz\": false,\n" + " \"aaa\": true\n" + " }\n" + " ]\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + + // Default is sorted. + EXPECT_EQ(json.ToString(), "{\"attributes\":[{\"aaa\":true,\"zzz\":false}]}"); +} + +TEST(FastSerdeJsonBridge, JsonBoolToProto) { + google::protobuf::Value expected_value = + ParseTextProtoOrDie(R"pb(bool_value: true)pb"); + constexpr absl::string_view kJsonString = "true"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.ToProtoValue(), IsOkAndHolds(EqualsProto(expected_value))); +} + +TEST(FastSerdeJsonBridge, JsonStringToProto) { + google::protobuf::Value expected_value = + ParseTextProtoOrDie(R"pb(string_value: "string")pb"); + constexpr absl::string_view kJsonString = "\"string\""; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.ToProtoValue(), IsOkAndHolds(EqualsProto(expected_value))); +} + +TEST(FastSerdeJsonBridge, JsonIntToProto) { + google::protobuf::Value expected_value = + ParseTextProtoOrDie(R"pb(number_value: 1234)pb"); + constexpr absl::string_view kJsonString = "1234"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.ToProtoValue(), IsOkAndHolds(EqualsProto(expected_value))); +} + +TEST(FastSerdeJsonBridge, JsonDoubleToProto) { + google::protobuf::Value expected_value = + ParseTextProtoOrDie(R"pb(number_value: 1337.1234)pb"); + constexpr absl::string_view kJsonString = "1337.1234"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.ToProtoValue(), IsOkAndHolds(EqualsProto(expected_value))); +} + +TEST(FastSerdeJsonBridge, JsonArrayToProto) { + google::protobuf::Value expected_value = ParseTextProtoOrDie(R"pb( + list_value { + values { string_value: "first" } + values { number_value: 2 } + values { string_value: "third" } + values { number_value: 4 } + } + )pb"); + + constexpr absl::string_view kJsonString = + "[\n" + " \"first\",\n" + " 2,\n" + " \"third\",\n" + " 4\n" + "]"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.ToProtoValue(), IsOkAndHolds(EqualsProto(expected_value))); +} + +TEST(FastSerdeJsonBridge, JsonNULLToProto) { + google::protobuf::Value expected_value = + ParseTextProtoOrDie(R"pb(null_value: NULL_VALUE)pb"); + constexpr absl::string_view kJsonString = "null"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.ToProtoValue(), IsOkAndHolds(EqualsProto(expected_value))); +} + +TEST(FastSerdeJsonBridge, JsonObjectToValue) { + google::protobuf::Value expected_value = ParseTextProtoOrDie(R"pb( + struct_value { + fields { + key: "firstName" + value { string_value: "John" } + } + fields { + key: "lastName" + value { string_value: "Doe" } + } + fields { + key: "value1" + value { number_value: 1.0 } + } + fields { + key: "value2" + value { number_value: 3.0 } + } + } + )pb"); + + constexpr absl::string_view kJsonString = + "{\n" + " \"firstName\": \"John\",\n" + " \"lastName\": \"Doe\",\n" + " \"value1\": 1.0,\n" + " \"value2\": 3.0\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.ToProtoValue(), IsOkAndHolds(EqualsProto(expected_value))); +} + +TEST(FastSerdeJsonBridge, JsonObjectToProtoStruct) { + google::protobuf::Struct expected_value = ParseTextProtoOrDie(R"pb( + fields { + key: "firstName" + value { string_value: "John" } + } + fields { + key: "lastName" + value { string_value: "Doe" } + } + fields { + key: "value1" + value { number_value: 1.0 } + } + fields { + key: "value2" + value { number_value: 3.0 } + } + )pb"); + + constexpr absl::string_view kJsonString = + "{\n" + " \"firstName\": \"John\",\n" + " \"lastName\": \"Doe\",\n" + " \"value1\": 1.0,\n" + " \"value2\": 3.0\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + EXPECT_THAT(json.ToProtoStruct(), IsOkAndHolds(EqualsProto(expected_value))); +} + +TEST(FastSerdeJsonBridge, CreateInt) { + FastSerdeJson json = FastSerdeJson::CreateInt(123); + + EXPECT_TRUE(json.IsNumber()); + EXPECT_TRUE(json.IsInt()); + EXPECT_THAT(json.GetInt(), IsOkAndHolds(123)); + EXPECT_EQ(json.ToString(), "123"); +} + +TEST(FastSerdeJsonBridge, CreateBool) { + FastSerdeJson json = FastSerdeJson::CreateBool(false); + + EXPECT_TRUE(json.IsBool()); + EXPECT_THAT(json.GetBool(), IsOkAndHolds(false)); + EXPECT_EQ(json.ToString(), "false"); +} + +TEST(FastSerdeJsonBridge, CreateDouble) { + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, + FastSerdeJson::CreateDouble(1337.1337)); + + EXPECT_TRUE(json.IsNumber()); + EXPECT_TRUE(json.IsDouble()); + EXPECT_THAT(json.GetDouble(), IsOkAndHolds(1337.1337)); + EXPECT_EQ(json.ToString(), "1337.1337"); +} + +TEST(FastSerdeJsonBridge, CreateDoubleWithNaN) { + EXPECT_THAT( + FastSerdeJson::CreateDouble(std::numeric_limits::quiet_NaN()), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(FastSerdeJsonBridge, CreateDoubleWithInfinity) { + EXPECT_THAT( + FastSerdeJson::CreateDouble(std::numeric_limits::infinity()), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(FastSerdeJsonBridge, CreateDoubleWithNegativeInfinity) { + EXPECT_THAT( + FastSerdeJson::CreateDouble(-std::numeric_limits::infinity()), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(FastSerdeJsonBridge, CreateNull) { + FastSerdeJson json = FastSerdeJson::CreateNull(); + + EXPECT_TRUE(json.IsNull()); + EXPECT_EQ(json.ToString(), "null"); +} + +TEST(FastSerdeJsonBridge, CreateString) { + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::CreateString("text")); + + EXPECT_TRUE(json.IsString()); + EXPECT_THAT(json.GetString(), IsOkAndHolds("text")); + EXPECT_EQ(json.ToString(), "\"text\""); +} + +TEST(FastSerdeJsonBridge, CreateArray) { + FastSerdeJson json = FastSerdeJson::CreateArray(); + + EXPECT_TRUE(json.IsArray()); + EXPECT_EQ(json.ToString(), "[]"); +} + +TEST(FastSerdeJsonBridge, HasField) { + constexpr absl::string_view kJsonString = + "{\n" + " \"value\": 10\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + + EXPECT_THAT(json.HasField("value"), IsOkAndHolds(true)); + EXPECT_THAT(json.HasField("not_existing"), IsOkAndHolds(false)); +} + +TEST(FastSerdeJsonBridge, HasFieldNotObject) { + constexpr absl::string_view kJsonString = "10"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json, FastSerdeJson::Parse(kJsonString)); + + EXPECT_THAT(json.HasField("value"), IsOkAndHolds(false)); +} + +TEST(FastSerdeJsonBridge, AddInt) { + FastSerdeJson json = FastSerdeJson::CreateObject(); + + EXPECT_OK(json.AddFieldInt("value", 123)); + EXPECT_THAT(json.HasField("value"), IsOkAndHolds(true)); + EXPECT_THAT(json.GetFieldInt("value"), IsOkAndHolds(123)); + EXPECT_EQ(json.ToString(), "{\"value\":123}"); +} + +TEST(FastSerdeJsonBridge, AddBool) { + FastSerdeJson json = FastSerdeJson::CreateObject(); + + EXPECT_OK(json.AddFieldBool("value", true)); + EXPECT_THAT(json.HasField("value"), IsOkAndHolds(true)); + EXPECT_THAT(json.GetFieldBool("value"), IsOkAndHolds(true)); + EXPECT_EQ(json.ToString(), "{\"value\":true}"); +} + +TEST(FastSerdeJsonBridge, AddDouble) { + FastSerdeJson json = FastSerdeJson::CreateObject(); + + EXPECT_OK(json.AddFieldDouble("value", 1337.1337)); + EXPECT_THAT(json.HasField("value"), IsOkAndHolds(true)); + EXPECT_THAT(json.GetFieldDouble("value"), IsOkAndHolds(1337.1337)); + EXPECT_EQ(json.ToString(), "{\"value\":1337.1337}"); +} + +TEST(FastSerdeJsonBridge, AddDoubleInvalid) { + FastSerdeJson json = FastSerdeJson::CreateObject(); + EXPECT_THAT( + json.AddFieldDouble("nan", std::numeric_limits::quiet_NaN()), + StatusIs(absl::StatusCode::kInvalidArgument)); + EXPECT_THAT( + json.AddFieldDouble("inf", std::numeric_limits::infinity()), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(FastSerdeJsonBridge, AddNull) { + FastSerdeJson json = FastSerdeJson::CreateObject(); + + EXPECT_OK(json.AddFieldNull("value")); + EXPECT_THAT(json.HasField("value"), IsOkAndHolds(true)); + EXPECT_EQ(json.ToString(), "{\"value\":null}"); +} + +TEST(FastSerdeJsonBridge, AddString) { + FastSerdeJson json = FastSerdeJson::CreateObject(); + + EXPECT_OK(json.AddFieldString("value", "text")); + EXPECT_THAT(json.HasField("value"), IsOkAndHolds(true)); + EXPECT_THAT(json.GetFieldString("value"), IsOkAndHolds("text")); + EXPECT_EQ(json.ToString(), "{\"value\":\"text\"}"); +} + +TEST(FastSerdeJsonBridge, AddObject) { + FastSerdeJson v1 = FastSerdeJson::CreateObject(); + EXPECT_OK(v1.AddFieldString("value", "text")); + + FastSerdeJson json = FastSerdeJson::CreateObject(); + + EXPECT_OK(json.AddFieldObject("obj", std::move(v1))); + EXPECT_THAT(json.HasField("obj"), IsOkAndHolds(true)); + EXPECT_EQ(json.ToString(), "{\"obj\":{\"value\":\"text\"}}"); +} + +TEST(FastSerdeJsonBridge, AddArray) { + ASSERT_OK_AND_ASSIGN(FastSerdeJson v1_text, + FastSerdeJson::CreateString("text")); + FastSerdeJson v2_int = FastSerdeJson::CreateInt(213); + ASSERT_OK_AND_ASSIGN(FastSerdeJson v3_double, + FastSerdeJson::CreateDouble(1337.1337)); + FastSerdeJson json = FastSerdeJson::CreateObject(); + + std::vector items; + items.push_back(std::move(v1_text)); + items.push_back(std::move(v2_int)); + items.push_back(std::move(v3_double)); + + ASSERT_OK(json.AddFieldArray("value", std::move(items))); + EXPECT_THAT(json.HasField("value"), IsOkAndHolds(true)); + ASSERT_OK_AND_ASSIGN(std::vector array, + json.GetFieldArray("value")); + ASSERT_EQ(array.size(), 3); + EXPECT_THAT(json.GetString(array[0]), IsOkAndHolds("text")); + EXPECT_THAT(json.GetInt(array[1]), IsOkAndHolds(213)); + EXPECT_THAT(json.GetDouble(array[2]), IsOkAndHolds(1337.1337)); + + EXPECT_EQ(json.ToString(), "{\"value\":[\"text\",213,1337.1337]}"); +} + +TEST(FastSerdeJsonBridge, EqualsSimple) { + constexpr absl::string_view kJsonString = + "{\n" + " \"firstName\": \"John\",\n" + " \"lastName\": \"Doe\"\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson first, FastSerdeJson::Parse(kJsonString)); + ASSERT_OK_AND_ASSIGN(FastSerdeJson second, FastSerdeJson::Parse(kJsonString)); + EXPECT_EQ(first, second); +} + +TEST(FastSerdeJsonBridge, EqualsDifferentOrder) { + constexpr absl::string_view kJsonString1 = + "{\n" + " \"firstName\": \"John\",\n" + " \"lastName\": \"Doe\"\n" + "}"; + constexpr absl::string_view kJsonString2 = + "{\n" + " \"lastName\": \"Doe\",\n" + " \"firstName\": \"John\"\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson first, FastSerdeJson::Parse(kJsonString1)); + ASSERT_OK_AND_ASSIGN(FastSerdeJson second, + FastSerdeJson::Parse(kJsonString2)); + EXPECT_EQ(first, second); +} + +TEST(FastSerdeJsonBridge, NotEqualsSimple) { + constexpr absl::string_view kJsonString1 = + "{\n" + " \"lastName\": \"Doe\"\n" + "}"; + constexpr absl::string_view kJsonString2 = + "{\n" + " \"firstName\": \"John\"\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson first, FastSerdeJson::Parse(kJsonString1)); + ASSERT_OK_AND_ASSIGN(FastSerdeJson second, + FastSerdeJson::Parse(kJsonString2)); + EXPECT_NE(first, second); +} + +TEST(FastSerdeJsonBridge, NotEqualsOneEmptyObj) { + constexpr absl::string_view kJsonString1 = "{}"; + constexpr absl::string_view kJsonString2 = + "{\n" + " \"firstName\": \"John\"\n" + "}"; + + ASSERT_OK_AND_ASSIGN(FastSerdeJson first, FastSerdeJson::Parse(kJsonString1)); + ASSERT_OK_AND_ASSIGN(FastSerdeJson second, + FastSerdeJson::Parse(kJsonString2)); + EXPECT_NE(first, second); +} + +// Note on handle semantics and safety across document instances: +// A NodeHandle is an opaque 64-bit index into a document's internal handle +// registry, representing a structural path from root rather than an absolute +// memory address or document-bound reference. By design, NodeHandle does not +// contain a document instance ID in order to keep handles lightweight (cheap to +// copy/move) and reusable across document clones. Consequently, using a handle +// ID on an unrelated document will fail with an out-of-bounds error if the ID +// exceeds that document's registry size, but if the unrelated document has +// created an identical handle ID index in its own registry, resolving the +// handle will evaluate against that document's path in its registry. +TEST(FastSerdeJsonBridge, NodeHandleSafetyAndValidity) { + ASSERT_OK_AND_ASSIGN(FastSerdeJson doc1, FastSerdeJson::Parse("{\"a\": 1}")); + ASSERT_OK_AND_ASSIGN(FastSerdeJson doc2, FastSerdeJson::Parse("{\"b\": 2}")); + + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle h1, doc1.GetField("a")); + + // Valid handle on doc1 + EXPECT_THAT(doc1.GetInt(h1), IsOkAndHolds(1)); + + // Using h1 from doc1 on doc2 fails because doc2 has not registered any child + // handles yet (its handle registry only contains the root handle at index 0). + EXPECT_THAT(doc2.GetInt(h1), StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(FastSerdeJsonBridge, MoveSemanticsAndCloneSubtree) { + ASSERT_OK_AND_ASSIGN(FastSerdeJson doc, + FastSerdeJson::Parse("{\"key\": \"value\"}")); + FastSerdeJson moved_doc = std::move(doc); + + EXPECT_THAT(moved_doc.GetFieldString("key"), IsOkAndHolds("value")); + + ASSERT_OK_AND_ASSIGN(FastSerdeJson cloned_doc, moved_doc.CloneSubtree()); + EXPECT_EQ(moved_doc.ToString(), cloned_doc.ToString()); + EXPECT_EQ(moved_doc, cloned_doc); +} + +TEST(FastSerdeJsonBridge, SubtreeClone) { + ASSERT_OK_AND_ASSIGN(FastSerdeJson doc, + FastSerdeJson::Parse("{\"sub\": {\"num\": 42}}")); + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle sub_h, doc.GetField("sub")); + + ASSERT_OK_AND_ASSIGN(FastSerdeJson sub_doc, doc.CloneSubtree(sub_h)); + EXPECT_EQ(sub_doc.ToString(), "{\"num\":42}"); + EXPECT_THAT(sub_doc.GetFieldInt("num"), IsOkAndHolds(42)); +} + +TEST(FastSerdeJsonBridge, InvalidHandleCloneFails) { + ASSERT_OK_AND_ASSIGN(FastSerdeJson doc1, FastSerdeJson::Parse("{\"a\": 1}")); + ASSERT_OK_AND_ASSIGN(FastSerdeJson doc2, FastSerdeJson::Parse("{\"b\": 2}")); + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle h1, doc1.GetField("a")); + + EXPECT_THAT(doc2.CloneSubtree(h1), + StatusIs(absl::StatusCode::kInvalidArgument)); +} + +TEST(FastSerdeJsonBridge, HandleValidityAfterMove) { + constexpr absl::string_view kJsonString = + "{\"root_field\": \"hello\", \"child\": {\"num\": 123}}"; + ASSERT_OK_AND_ASSIGN(FastSerdeJson parsed_doc, + FastSerdeJson::Parse(kJsonString)); + std::optional doc = std::move(parsed_doc); + + FastSerdeJson::NodeHandle root_handle = {}; + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle child_handle, + doc->GetField("child", root_handle)); + ASSERT_OK_AND_ASSIGN(FastSerdeJson::NodeHandle num_handle, + doc->GetField("num", child_handle)); + + // Perform initial lookups to populate cached pointers in HandleRegistry + EXPECT_THAT(doc->GetFieldString("root_field", root_handle), + IsOkAndHolds("hello")); + EXPECT_THAT(doc->GetInt(num_handle), IsOkAndHolds(123)); + + // Move the document instance to a new location in memory and explicitly reset + // the old optional object. This destroys the previous object memory location, + // making memory bugs more apparent if cached pointers were not invalidated. + FastSerdeJson moved_doc = std::move(*doc); + doc.reset(); + + // Verifying both root and non-root handles succeed after move, confirming + // HandleRegistry detects the move and invalidates cached pointers properly. + EXPECT_THAT(moved_doc.GetFieldString("root_field", root_handle), + IsOkAndHolds("hello")); + EXPECT_THAT(moved_doc.GetInt(num_handle), IsOkAndHolds(123)); +} + +TEST(FastSerdeJsonBridge, FloatPrecisionDifference) { + double d1 = 0.082788195087703992; + std::string s1 = "0.082788195087703992"; + double d2 = 0.082788195087704; + std::string s2 = "0.082788195087704"; + + // FastSerdeJson. + ASSERT_OK_AND_ASSIGN(FastSerdeJson json1, FastSerdeJson::Parse(s1)); + ASSERT_OK_AND_ASSIGN(double val1, json1.GetDouble()); + + ASSERT_OK_AND_ASSIGN(FastSerdeJson json2, FastSerdeJson::Parse(s2)); + ASSERT_OK_AND_ASSIGN(double val2, json2.GetDouble()); + + // Nlohmann. + nlohmann::json nlohmann_json1 = nlohmann::json::parse(s1); + double nlohmann_val1 = nlohmann_json1.get(); + + nlohmann::json nlohmann_json2 = nlohmann::json::parse(s2); + double nlohmann_val2 = nlohmann_json2.get(); + + // FastSerdeJson parses both strings to the same value. + EXPECT_EQ(val1, val2); + // Standard C++ literals distinguish them. + EXPECT_NE(d1, d2); + // FastSerdeJson parses both strings to the same value, which matches the less + // precise literal in C++. + EXPECT_EQ(val1, d2); + EXPECT_NE(val1, d1); + // Nlohmann distinguishes them, matching standard C++ literals. + EXPECT_NE(nlohmann_val1, nlohmann_val2); + EXPECT_EQ(nlohmann_val1, d1); + EXPECT_EQ(nlohmann_val2, d2); +} + +} // namespace diff --git a/serde_json/rust/fast_json.rs b/serde_json/rust/fast_json.rs new file mode 100644 index 0000000..3153861 --- /dev/null +++ b/serde_json/rust/fast_json.rs @@ -0,0 +1,937 @@ +use serde::Serialize; +use serde_json::{Number, Value}; +use std::cell::{Cell, RefCell}; +use std::collections::HashMap; + +pub type Status = status_wrapper::StatusWrapper; + +#[inline(always)] +fn ok() -> Status { + status::OkStatus().into() +} + +#[inline(always)] +fn internal_error(message: impl Into) -> Status { + status::internal(message.into()).into() +} + +#[inline(always)] +fn invalid_argument_error(message: impl Into) -> Status { + status::invalid_argument(message.into()).into() +} + +#[inline(always)] +fn out_of_range_error(message: impl Into) -> Status { + status::out_of_range(message.into()).into() +} + +#[inline(always)] +fn failed_precondition_error(message: impl Into) -> Status { + status::failed_precondition(message.into()).into() +} + +#[inline(always)] +fn invalid_handle_error(message: impl Into) -> Status { + status::invalid_argument(message.into()).into() +} + +/// An opaque 64-bit integer handle representing a specific node within a JSON document tree. +#[repr(C)] +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct NodeHandle { + id: u64, +} + +impl NodeHandle { + pub fn root() -> Self { + Self { id: 0 } + } +} + +/// Represents a single edge/step from a parent JSON node to a child node (object field or array index). +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +enum PathElement { + Root, + Field(Box), + Index(usize), +} + +/// A lookup key for child node handles, identifying a child by its parent handle ID and the step taken from parent to child. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +struct PathKey { + parent_id: NodeHandle, + element: PathElement, +} + +/// Metadata stored for each handle, tracking its parent handle, path element step, and an optional cached pointer into the AST. +#[derive(Clone, Debug)] +struct HandleData { + parent_id: NodeHandle, + element: PathElement, + ptr: Cell<*const Value>, +} + +impl PartialEq for HandleData { + fn eq(&self, other: &Self) -> bool { + self.parent_id == other.parent_id && self.element == other.element + } +} + +impl Eq for HandleData {} + +/// Registry maintaining bi-directional mapping between handles, their parent relationships, and cached AST pointers. +#[derive(Clone, Debug, PartialEq, Eq)] +struct HandleRegistry { + handle_to_data: RefCell>, + key_to_handle: RefCell>, + owner_addr: Cell, +} + +impl Default for HandleRegistry { + fn default() -> Self { + let root_handle = NodeHandle::root(); + let root_data = HandleData { + parent_id: root_handle, + element: PathElement::Root, + ptr: Cell::new(std::ptr::null()), + }; + let root_key = PathKey { parent_id: root_handle, element: PathElement::Root }; + let mut key_to_handle = HashMap::new(); + key_to_handle.insert(root_key, root_handle); + Self { + handle_to_data: RefCell::new(vec![root_data]), + key_to_handle: RefCell::new(key_to_handle), + owner_addr: Cell::new(0), + } + } +} + +impl HandleRegistry { + pub fn get_or_create_child_handle( + &self, + parent_id: NodeHandle, + element: PathElement, + ptr: *const Value, + ) -> Result { + let key = PathKey { parent_id, element }; + let mut key_to_handle = self.key_to_handle.borrow_mut(); + if let Some(&handle) = key_to_handle.get(&key) { + let handle_to_data = self.handle_to_data.borrow(); + if let Some(data) = handle_to_data.get(handle.id as usize) { + // Updating `data.ptr` is safe because the handle refers to the same immutable node + // in `self.value`. The cached pointer may have been invalidated (set to null) by a mutation or clone. + data.ptr.set(ptr); + } + return Ok(handle); + } + let mut handle_to_data = self.handle_to_data.borrow_mut(); + let id = handle_to_data.len() as u64; + if id == u64::MAX { + return Err(failed_precondition_error( + "Handle registry overflow: exceeded 2^64 unique handles", + )); + } + let handle = NodeHandle { id }; + handle_to_data.push(HandleData { + parent_id, + element: key.element.clone(), + ptr: Cell::new(ptr), + }); + key_to_handle.insert(key, handle); + Ok(handle) + } + + pub fn invalidate_ptrs(&self) { + self.owner_addr.set(0); + let handle_to_data = self.handle_to_data.borrow(); + for data in handle_to_data.iter() { + data.ptr.set(std::ptr::null()); + } + } +} + +/// A JSON document with an opaque handle-based interface for navigation and modification. +/// Note: `FastSerdeJson` is NOT thread-safe. Its internal `HandleRegistry` uses `RefCell` +/// and `Cell` for caching pointer lookups. Concurrent access from multiple threads will lead +/// to runtime panics or undefined behavior. +pub struct FastSerdeJson { + value: Value, + registry: HandleRegistry, +} + +impl Clone for FastSerdeJson { + fn clone(&self) -> Self { + let cloned = Self { value: self.value.clone(), registry: self.registry.clone() }; + cloned.registry.invalidate_ptrs(); + cloned + } +} + +// Note on CppVec* newtype wrappers around `cc_std::std::vector`: +// While using `cc_std::std::vector` directly across FFI boundaries works cleanly +// for straightforward parameters and direct returns, returning `Result, Status>` +// currently triggers a Crubit binding generation limitation (b/259749095 — "Generic types are not +// supported yet inside Result"). By defining concrete, non-generic newtype structs and annotating +// them with `#[crubit_annotate::cpp_layout_equivalent]`, we bypass the generic Result error in +// Rust while Crubit seamlessly maps them to standard `std::vector` in C++ without wrapper overhead. + +#[crubit_annotate::cpp_layout_equivalent( + cpp_type = "::std::vector<::std::uint8_t>", + include_path = "" +)] +#[derive(Default)] +#[repr(C)] +pub struct CppVecU8(pub cc_std::std::vector); + +impl CppVecU8 { + pub fn new() -> Self { + Self::default() + } +} + +impl From> for CppVecU8 { + fn from(v: Vec) -> Self { + Self(v.into()) + } +} + +impl From<&[u8]> for CppVecU8 { + fn from(s: &[u8]) -> Self { + Self(s.into()) + } +} + +#[crubit_annotate::cpp_layout_equivalent( + cpp_type = "::std::vector<::std::vector<::std::uint8_t>>", + include_path = "" +)] +#[repr(C)] +pub struct CppVecVecU8(pub cc_std::std::vector>); + +impl std::iter::FromIterator for CppVecVecU8 { + fn from_iter>(iter: I) -> Self { + Self(iter.into_iter().map(|v| v.0).collect()) + } +} + +#[crubit_annotate::cpp_layout_equivalent( + cpp_type = "::std::vector<::fast_serde_json_rs::NodeHandle>", + include_path = "" +)] +#[repr(C)] +pub struct CppVecNodeHandle(pub cc_std::std::vector); + +impl From> for CppVecNodeHandle { + fn from(v: Vec) -> Self { + Self(v.into()) + } +} + +#[crubit_annotate::cpp_layout_equivalent( + cpp_type = "::std::vector<::fast_serde_json_rs::FastSerdeJson>", + include_path = "" +)] +#[repr(C)] +pub struct CppVecFastSerdeJson(pub cc_std::std::vector); + +impl IntoIterator for CppVecFastSerdeJson { + type Item = FastSerdeJson; + type IntoIter = as IntoIterator>::IntoIter; + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl Default for FastSerdeJson { + fn default() -> Self { + Self::new_null() + } +} + +impl From for FastSerdeJson { + fn from(v: i64) -> Self { + Self::from_i64(v) + } +} + +impl From for FastSerdeJson { + fn from(v: bool) -> Self { + Self::from_bool(v) + } +} + +impl TryFrom for FastSerdeJson { + type Error = Status; + + fn try_from(v: f64) -> Result { + Self::try_from_f64(v) + } +} + +impl TryFrom<&[u8]> for FastSerdeJson { + type Error = Status; + + fn try_from(v: &[u8]) -> Result { + Self::try_from_utf8(v) + } +} + +impl PartialEq for FastSerdeJson { + fn eq(&self, other: &Self) -> bool { + self.is_json_equal(other) + } +} + +impl Eq for FastSerdeJson {} + +impl std::fmt::Debug for FastSerdeJson { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "FastSerdeJson({:?})", self.value) + } +} + +macro_rules! traverse_path_impl { + ($root:expr, $path:expr, $as_array:ident, $get_elem:ident, $get_field:ident) => {{ + let mut curr = $root; + for elem in $path { + match elem { + PathElement::Root => {} + PathElement::Index(idx) => match curr.$as_array() { + Some(arr) => { + let arr_len = arr.len(); + match arr.$get_elem(*idx) { + Some(val) => curr = val, + None => { + return Err(invalid_handle_error(format!( + "Index {} out of bounds for array of length {}", + idx, arr_len + ))); + } + } + } + None => { + return Err(invalid_handle_error( + "Path element is index, but current JSON node is not an array", + )); + } + }, + PathElement::Field(name) => match curr.$get_field(name.as_ref()) { + Some(val) => curr = val, + None => { + return Err(invalid_handle_error(format!( + "Field '{}' not found in JSON object", + name + ))); + } + }, + } + } + Ok(curr) + }}; +} + +fn traverse_path<'a>(root: &'a Value, path: &[PathElement]) -> Result<&'a Value, Status> { + traverse_path_impl!(root, path, as_array, get, get) +} + +fn traverse_path_mut<'a>( + root: &'a mut Value, + path: &[PathElement], +) -> Result<&'a mut Value, Status> { + traverse_path_impl!(root, path, as_array_mut, get_mut, get_mut) +} + +struct PathFromAncestor { + ancestor: NodeHandle, + path: Vec, +} + +impl FastSerdeJson { + fn new(value: Value) -> Self { + Self { value, registry: HandleRegistry::default() } + } + + fn get_path_from_ancestor(&self, handle: NodeHandle) -> Result { + let handle_to_data = self.registry.handle_to_data.borrow(); + let mut curr_id = handle.id as usize; + let mut elements = Vec::new(); + while curr_id != 0 { + let data = handle_to_data.get(curr_id).ok_or_else(|| { + invalid_handle_error(format!("Invalid NodeHandle ID: {}", curr_id)) + })?; + + // Early-stop if we discover a valid cached ancestor pointer (excluding target handle itself) + if curr_id != handle.id as usize && !data.ptr.get().is_null() { + elements.reverse(); + return Ok(PathFromAncestor { + ancestor: NodeHandle { id: curr_id as u64 }, + path: elements, + }); + } + + elements.push(data.element.clone()); + curr_id = data.parent_id.id as usize; + } + elements.reverse(); + Ok(PathFromAncestor { ancestor: NodeHandle::root(), path: elements }) + } + + fn get_path_elements(&self, handle: NodeHandle) -> Result, Status> { + Ok(self.get_path_from_ancestor(handle)?.path) + } + + fn resolve_node(&self, handle: NodeHandle) -> Result<&Value, Status> { + let current_addr = std::ptr::from_ref(self) as usize; + let cached_addr = self.registry.owner_addr.get(); + if cached_addr != 0 && cached_addr != current_addr { + // FastSerdeJson instance has moved in memory; invalidate all cached raw pointers. + self.registry.invalidate_ptrs(); + } + self.registry.owner_addr.set(current_addr); + + let handle_to_data = self.registry.handle_to_data.borrow(); + let curr_id = handle.id as usize; + let data = handle_to_data + .get(curr_id) + .ok_or_else(|| invalid_handle_error(format!("Invalid NodeHandle ID: {}", handle.id)))?; + + let cached_ptr = data.ptr.get(); + if !cached_ptr.is_null() { + // SAFETY: [Why unsafe] Caching raw pointers in `HandleRegistry` avoids repeatedly traversing + // the AST from the root on every lookup, reducing access time from O(depth) to O(1). + // + // Soundness guarantee relies on two core properties: + // 1. Pointer validity: The cached pointer references a valid, unmodified object in memory because: + // - The pointer is only ever assigned from `Value` objects owned by this `FastSerdeJson` instance. + // - Since assignment, this instance has not moved in memory; otherwise the relocation is detected + // via `owner_addr` and all cached pointers are immediately invalidated. + // - This instance is not a clone of another document, as cloning invokes `invalidate_ptrs()`. + // - Any operations that mutate `self.value` also invoke `invalidate_ptrs()`, clearing all cached pointers. + // 2. Aliasing: Obtaining an immutable reference here is sound because: + // - `resolve_node` borrows `&'a self` immutably, ensuring no concurrent mutable references exist + // to `self.value` or any of its owned child nodes. + // - Via lifetime elision, the signature is interpreted as `(&'a self, ...) -> Result<&'a Value, Status>`. + // This guarantees the returned reference cannot outlive `&self`, preventing invalid aliasing. + return Ok(unsafe { &*cached_ptr }); + } + drop(handle_to_data); + + let path_info = self.get_path_from_ancestor(handle)?; + let ancestor_node = if path_info.ancestor.id == 0 { + &self.value + } else { + let handle_to_data = self.registry.handle_to_data.borrow(); + let anc_data = &handle_to_data[path_info.ancestor.id as usize]; + let ptr = anc_data.ptr.get(); + if ptr.is_null() { + &self.value + } else { + // SAFETY: [Why unsafe] Caching raw pointers in `HandleRegistry` avoids repeatedly traversing + // the AST from the root on every lookup, reducing access time from O(depth) to O(1). + // + // Soundness guarantee relies on two core properties: + // 1. Pointer validity: The cached pointer references a valid, unmodified object in memory because: + // - The pointer is only ever assigned from `Value` objects owned by this `FastSerdeJson` instance. + // - Since assignment, this instance has not moved in memory; otherwise the relocation is detected + // via `owner_addr` and all cached pointers are immediately invalidated. + // - This instance is not a clone of another document, as cloning invokes `invalidate_ptrs()`. + // - Any operations that mutate `self.value` also invoke `invalidate_ptrs()`, clearing all cached pointers. + // 2. Aliasing: Obtaining an immutable reference here is sound because: + // - `resolve_node` borrows `&'a self` immutably, ensuring no concurrent mutable references exist + // to `self.value` or any of its owned child nodes. + // - Via lifetime elision, the signature is interpreted as `(&'a self, ...) -> Result<&'a Value, Status>`. + // This guarantees the returned reference cannot outlive `&self`, preventing invalid aliasing. + unsafe { &*ptr } + } + }; + + let curr = traverse_path(ancestor_node, &path_info.path)?; + let handle_to_data = self.registry.handle_to_data.borrow(); + if let Some(data) = handle_to_data.get(handle.id as usize) { + data.ptr.set(curr as *const _); + } + Ok(curr) + } + + fn resolve_node_mut(&mut self, handle: NodeHandle) -> Result<&mut Value, Status> { + self.registry.invalidate_ptrs(); + let path = self.get_path_elements(handle)?; + traverse_path_mut(&mut self.value, &path) + } + + pub fn clone_subtree(&self, handle: NodeHandle) -> Result { + if handle.id == 0 { + Ok(self.clone()) + } else { + let node = self.resolve_node(handle)?; + Ok(FastSerdeJson::new(node.clone())) + } + } + + pub fn try_parse(raw_data: &[u8]) -> Result { + match serde_json::from_slice(raw_data) { + Ok(value) => Ok(Self::new(value)), + Err(err) => Err(invalid_argument_error(err.to_string())), + } + } + + pub fn new_object() -> Self { + Self::new(serde_json::json!({})) + } + + pub fn new_array() -> Self { + Self::new(serde_json::json!([])) + } + + pub fn from_i64(v: i64) -> Self { + Self::new(serde_json::json!(v)) + } + + pub fn try_from_f64(v: f64) -> Result { + match Number::from_f64(v) { + Some(n) => Ok(Self::new(Value::Number(n))), + None => Err(invalid_argument_error(format!("Invalid JSON number: {}", v))), + } + } + + pub fn from_bool(v: bool) -> Self { + Self::new(serde_json::json!(v)) + } + + pub fn new_null() -> Self { + Self::new(Value::Null) + } + + pub fn try_from_utf8(raw_value: &[u8]) -> Result { + let value = match std::str::from_utf8(raw_value) { + Ok(data) => data, + Err(err) => return Err(invalid_argument_error(err.to_string())), + }; + Ok(Self::new(serde_json::json!(value))) + } + + pub fn get_field( + &self, + raw_field_name: &[u8], + handle: NodeHandle, + ) -> Result { + let field_name = match std::str::from_utf8(raw_field_name) { + Ok(field_name) => field_name, + Err(err) => return Err(invalid_argument_error(err.to_string())), + }; + let node = self.resolve_node(handle)?; + match node.get(field_name) { + Some(child_node) => self.registry.get_or_create_child_handle( + handle, + PathElement::Field(field_name.into()), + child_node as *const _, + ), + None => Err(failed_precondition_error(format!( + "Field '{}' not found in JSON object", + field_name + ))), + } + } + + pub fn get_field_string( + &self, + raw_field_name: &[u8], + handle: NodeHandle, + ) -> Result { + let field_name = match std::str::from_utf8(raw_field_name) { + Ok(field_name) => field_name, + Err(err) => return Err(invalid_argument_error(err.to_string())), + }; + let node = self.resolve_node(handle)?; + match node.get(field_name).and_then(|v| v.as_str()) { + Some(s) => Ok(s.as_bytes().into()), + None => Err(failed_precondition_error(format!("Field '{}' is not string", field_name))), + } + } + + pub fn get_field_bool( + &self, + raw_field_name: &[u8], + handle: NodeHandle, + ) -> Result { + let field_name = match std::str::from_utf8(raw_field_name) { + Ok(field_name) => field_name, + Err(err) => return Err(invalid_argument_error(err.to_string())), + }; + let node = self.resolve_node(handle)?; + match node.get(field_name).and_then(|v| v.as_bool()) { + Some(b) => Ok(b), + None => { + Err(failed_precondition_error(format!("Field '{}' is not boolean", field_name))) + } + } + } + + pub fn get_field_int(&self, raw_field_name: &[u8], handle: NodeHandle) -> Result { + let field_name = match std::str::from_utf8(raw_field_name) { + Ok(field_name) => field_name, + Err(err) => return Err(invalid_argument_error(err.to_string())), + }; + let node = self.resolve_node(handle)?; + match node.get(field_name).and_then(|v| v.as_i64()) { + Some(i) => Ok(i), + None => { + Err(failed_precondition_error(format!("Field '{}' is not integer", field_name))) + } + } + } + + pub fn get_field_double( + &self, + raw_field_name: &[u8], + handle: NodeHandle, + ) -> Result { + let field_name = match std::str::from_utf8(raw_field_name) { + Ok(field_name) => field_name, + Err(err) => return Err(invalid_argument_error(err.to_string())), + }; + let node = self.resolve_node(handle)?; + match node.get(field_name).and_then(|v| v.as_f64()) { + Some(f) => Ok(f), + None => Err(failed_precondition_error(format!("Field '{}' is not double", field_name))), + } + } + + pub fn get_field_object( + &self, + raw_field_name: &[u8], + handle: NodeHandle, + ) -> Result { + let field_name = match std::str::from_utf8(raw_field_name) { + Ok(field_name) => field_name, + Err(err) => return Err(invalid_argument_error(err.to_string())), + }; + let node = self.resolve_node(handle)?; + match node.get(field_name) { + Some(child_node @ Value::Object(_)) => self.registry.get_or_create_child_handle( + handle, + PathElement::Field(field_name.into()), + child_node as *const _, + ), + _ => Err(failed_precondition_error(format!("Field '{}' is not object", field_name))), + } + } + + pub fn get_field_array( + &self, + raw_field_name: &[u8], + handle: NodeHandle, + ) -> Result { + let field_name = match std::str::from_utf8(raw_field_name) { + Ok(field_name) => field_name, + Err(err) => return Err(invalid_argument_error(err.to_string())), + }; + let node = self.resolve_node(handle)?; + match node.get(field_name) { + Some(field_val @ Value::Array(a)) => { + let field_handle = self.registry.get_or_create_child_handle( + handle, + PathElement::Field(field_name.into()), + field_val as *const _, + )?; + let mut handles = Vec::with_capacity(a.len()); + for (i, elem) in a.iter().enumerate() { + handles.push(self.registry.get_or_create_child_handle( + field_handle, + PathElement::Index(i), + elem as *const _, + )?); + } + Ok(handles.into()) + } + _ => Err(failed_precondition_error(format!("Field '{}' is not array", field_name))), + } + } + + pub fn get_field_array_element( + &self, + raw_field_name: &[u8], + index: usize, + handle: NodeHandle, + ) -> Result { + let field_name = match std::str::from_utf8(raw_field_name) { + Ok(field_name) => field_name, + Err(err) => return Err(invalid_argument_error(err.to_string())), + }; + let node = self.resolve_node(handle)?; + match node.get(field_name) { + Some(child_node @ Value::Array(a)) => match a.get(index) { + Some(elem_node) => { + let field_handle = self.registry.get_or_create_child_handle( + handle, + PathElement::Field(field_name.into()), + child_node as *const _, + )?; + self.registry.get_or_create_child_handle( + field_handle, + PathElement::Index(index), + elem_node as *const _, + ) + } + None => Err(out_of_range_error(format!( + "Index {} out of bounds for array field '{}' of length {}", + index, + field_name, + a.len() + ))), + }, + _ => Err(failed_precondition_error(format!("Field '{}' is not array", field_name))), + } + } + + pub fn get_bool(&self, handle: NodeHandle) -> Result { + let node = self.resolve_node(handle)?; + match node.as_bool() { + Some(b) => Ok(b), + None => Err(failed_precondition_error("This object is not boolean")), + } + } + + pub fn get_int(&self, handle: NodeHandle) -> Result { + let node = self.resolve_node(handle)?; + match node.as_i64() { + Some(i) => Ok(i), + None => Err(failed_precondition_error("This object is not integer")), + } + } + + pub fn get_double(&self, handle: NodeHandle) -> Result { + let node = self.resolve_node(handle)?; + match node.as_f64() { + Some(f) => Ok(f), + None => Err(failed_precondition_error("This object is not double")), + } + } + + pub fn get_string(&self, handle: NodeHandle) -> Result { + let node = self.resolve_node(handle)?; + match node.as_str() { + Some(s) => Ok(s.as_bytes().into()), + None => Err(failed_precondition_error("This object is not string")), + } + } + + pub fn get_array(&self, handle: NodeHandle) -> Result { + let node = self.resolve_node(handle)?; + match node.as_array() { + Some(a) => { + let mut handles = Vec::with_capacity(a.len()); + for (i, elem) in a.iter().enumerate() { + handles.push(self.registry.get_or_create_child_handle( + handle, + PathElement::Index(i), + elem as *const _, + )?); + } + Ok(handles.into()) + } + None => Err(failed_precondition_error("This object is not array")), + } + } + + pub fn get_array_element( + &self, + index: usize, + handle: NodeHandle, + ) -> Result { + let node = self.resolve_node(handle)?; + match node.as_array() { + Some(a) => match a.get(index) { + Some(child_node) => self.registry.get_or_create_child_handle( + handle, + PathElement::Index(index), + child_node as *const _, + ), + None => Err(out_of_range_error(format!( + "Index {} out of bounds for array of length {}", + index, + a.len() + ))), + }, + None => Err(failed_precondition_error("This object is not array")), + } + } + + pub fn is_empty(&self, handle: NodeHandle) -> Result { + let node = self.resolve_node(handle)?; + let empty = match node { + Value::Null => true, + Value::Array(arr) => arr.is_empty(), + Value::Object(obj) => obj.is_empty(), + _ => false, + }; + Ok(empty) + } + + pub fn is_null(&self, handle: NodeHandle) -> bool { + self.resolve_node(handle).map(|n| n.is_null()).unwrap_or(false) + } + + pub fn is_boolean(&self, handle: NodeHandle) -> bool { + self.resolve_node(handle).map(|n| n.is_boolean()).unwrap_or(false) + } + + pub fn is_number(&self, handle: NodeHandle) -> bool { + self.resolve_node(handle).map(|n| n.is_number()).unwrap_or(false) + } + + pub fn is_i64(&self, handle: NodeHandle) -> bool { + self.resolve_node(handle).map(|n| n.is_i64()).unwrap_or(false) + } + + pub fn is_f64(&self, handle: NodeHandle) -> bool { + self.resolve_node(handle).map(|n| n.is_f64()).unwrap_or(false) + } + + pub fn is_string(&self, handle: NodeHandle) -> bool { + self.resolve_node(handle).map(|n| n.is_string()).unwrap_or(false) + } + + pub fn is_array(&self, handle: NodeHandle) -> bool { + self.resolve_node(handle).map(|n| n.is_array()).unwrap_or(false) + } + + pub fn is_object(&self, handle: NodeHandle) -> bool { + self.resolve_node(handle).map(|n| n.is_object()).unwrap_or(false) + } + + pub fn to_string(&self, sort_keys: bool, handle: NodeHandle) -> CppVecU8 { + let node = match self.resolve_node(handle) { + Ok(n) => n, + Err(_) => return CppVecU8::new(), + }; + if sort_keys { + let mut val = node.clone(); + val.sort_all_objects(); + return val.to_string().into_bytes().into(); + } + node.to_string().into_bytes().into() + } + + pub fn keys(&self, handle: NodeHandle) -> Result { + let node = self.resolve_node(handle)?; + let object = match node.as_object() { + Some(o) => o, + None => return Err(failed_precondition_error("This value is not an object")), + }; + let keys: CppVecVecU8 = object.keys().map(|k| k.as_bytes().into()).collect(); + Ok(keys) + } + + pub fn has_field(&self, raw_field_name: &[u8], handle: NodeHandle) -> Result { + let node = self.resolve_node(handle)?; + match std::str::from_utf8(raw_field_name) { + Ok(field_name) => Ok(node.get(field_name).is_some()), + Err(err) => Err(invalid_argument_error(err.to_string())), + } + } + + fn add_field_value( + &mut self, + raw_field_name: &[u8], + value: Value, + handle: NodeHandle, + ) -> Status { + let field_name = match std::str::from_utf8(raw_field_name) { + Ok(field) => field, + Err(err) => return invalid_argument_error(err.to_string()), + }; + let node = match self.resolve_node_mut(handle) { + Ok(n) => n, + Err(err) => return err, + }; + if let Value::Object(map) = node { + map.insert(field_name.to_string(), value); + ok() + } else { + internal_error("JSON value is not an object") + } + } + + pub fn add_field( + &mut self, + raw_field_name: &[u8], + value: T, + handle: NodeHandle, + ) -> Status { + match serde_json::to_value(value) { + Ok(json_value) => self.add_field_value(raw_field_name, json_value, handle), + Err(err) => invalid_argument_error(format!("Failed to serialize value: {}", err)), + } + } + + pub fn add_field_int( + &mut self, + raw_field_name: &[u8], + value: i64, + handle: NodeHandle, + ) -> Status { + self.add_field_value(raw_field_name, serde_json::json!(value), handle) + } + + pub fn add_field_bool( + &mut self, + raw_field_name: &[u8], + value: bool, + handle: NodeHandle, + ) -> Status { + self.add_field_value(raw_field_name, serde_json::json!(value), handle) + } + + pub fn add_field_double( + &mut self, + raw_field_name: &[u8], + value: f64, + handle: NodeHandle, + ) -> Status { + match Number::from_f64(value) { + Some(n) => self.add_field_value(raw_field_name, Value::Number(n), handle), + None => invalid_argument_error(format!("Invalid JSON number: {}", value)), + } + } + + pub fn add_field_null(&mut self, raw_field_name: &[u8], handle: NodeHandle) -> Status { + self.add_field_value(raw_field_name, Value::Null, handle) + } + + pub fn add_field_string( + &mut self, + raw_field_name: &[u8], + value_raw: &[u8], + handle: NodeHandle, + ) -> Status { + let value = match std::str::from_utf8(value_raw) { + Ok(value) => value, + Err(err) => return invalid_argument_error(err.to_string()), + }; + self.add_field_value(raw_field_name, serde_json::json!(value), handle) + } + + pub fn add_field_object( + &mut self, + raw_field_name: &[u8], + obj: FastSerdeJson, + handle: NodeHandle, + ) -> Status { + self.add_field_value(raw_field_name, obj.value, handle) + } + + pub fn add_field_array( + &mut self, + raw_field_name: &[u8], + items: CppVecFastSerdeJson, + handle: NodeHandle, + ) -> Status { + let vec_values: Vec = items.into_iter().map(|item| item.value).collect(); + self.add_field_value(raw_field_name, Value::Array(vec_values), handle) + } + + pub fn is_json_equal(&self, other: &Self) -> bool { + self.value == other.value + } +} diff --git a/serde_json/serde_differential_fuzzer.cc b/serde_json/serde_differential_fuzzer.cc new file mode 100644 index 0000000..fef658b --- /dev/null +++ b/serde_json/serde_differential_fuzzer.cc @@ -0,0 +1,132 @@ +#include +#include +#include +#include + +#include "fast_serde_json_bridge.h" +#include "serde_json_bridge.h" +#include "testing/base/public/gmock.h" +#include "testing/base/public/gunit.h" +#include "testing/fuzzing/fuzztest.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" + +namespace security::json { +namespace { + +using ::security::json::fast_serde_json_bridge::FastSerdeJson; +using ::security::json::serde_json_bridge::SerdeJson; + +void WalkAndCompare(const SerdeJson& s_doc, FastSerdeJson& f_doc, + FastSerdeJson::NodeHandle handle = {}) { + EXPECT_EQ(s_doc.IsNull(), f_doc.IsNull(handle)); + EXPECT_EQ(s_doc.IsObject(), f_doc.IsObject(handle)); + EXPECT_EQ(s_doc.IsArray(), f_doc.IsArray(handle)); + EXPECT_EQ(s_doc.IsString(), f_doc.IsString(handle)); + EXPECT_EQ(s_doc.IsNumber(), f_doc.IsNumber(handle)); + EXPECT_EQ(s_doc.IsInt(), f_doc.IsInt(handle)); + EXPECT_EQ(s_doc.IsDouble(), f_doc.IsDouble(handle)); + EXPECT_EQ(s_doc.IsBool(), f_doc.IsBool(handle)); + + ASSERT_OK_AND_ASSIGN(bool f_empty, f_doc.IsEmpty(handle)); + EXPECT_EQ(s_doc.IsEmpty(), f_empty); + + if (s_doc.IsInt()) { + absl::StatusOr s_val = s_doc.GetInt(); + absl::StatusOr f_val = f_doc.GetInt(handle); + EXPECT_EQ(s_val.ok(), f_val.ok()); + if (s_val.ok() && f_val.ok()) { + EXPECT_EQ(*s_val, *f_val); + } + } + if (s_doc.IsDouble()) { + absl::StatusOr s_val = s_doc.GetDouble(); + absl::StatusOr f_val = f_doc.GetDouble(handle); + EXPECT_EQ(s_val.ok(), f_val.ok()); + if (s_val.ok() && f_val.ok()) { + EXPECT_DOUBLE_EQ(*s_val, *f_val); + } + } + if (s_doc.IsBool()) { + absl::StatusOr s_val = s_doc.GetBool(); + absl::StatusOr f_val = f_doc.GetBool(handle); + EXPECT_EQ(s_val.ok(), f_val.ok()); + if (s_val.ok() && f_val.ok()) { + EXPECT_EQ(*s_val, *f_val); + } + } + if (s_doc.IsString()) { + absl::StatusOr s_val = s_doc.GetString(); + absl::StatusOr f_val = f_doc.GetString(handle); + EXPECT_EQ(s_val.ok(), f_val.ok()); + if (s_val.ok() && f_val.ok()) { + EXPECT_EQ(*s_val, *f_val); + } + } + + EXPECT_EQ(s_doc.ToString(/*sort_keys=*/true), + f_doc.ToString(/*sort_keys=*/true, handle)); + EXPECT_EQ(s_doc.ToString(/*sort_keys=*/false), + f_doc.ToString(/*sort_keys=*/false, handle)); + + if (s_doc.IsObject()) { + absl::StatusOr> s_keys = s_doc.GetKeys(); + absl::StatusOr> f_keys = f_doc.GetKeys(handle); + ASSERT_EQ(s_keys.ok(), f_keys.ok()); + if (!s_keys.ok() || !f_keys.ok()) return; + + EXPECT_EQ(*s_keys, *f_keys); + + for (const std::string& key : *s_keys) { + absl::StatusOr s_child = s_doc.GetField(key); + absl::StatusOr f_child_handle = + f_doc.GetField(key, handle); + ASSERT_EQ(s_child.ok(), f_child_handle.ok()); + if (s_child.ok() && f_child_handle.ok()) { + WalkAndCompare(*s_child, f_doc, *f_child_handle); + } + + absl::StatusOr s_has = s_doc.HasField(key); + absl::StatusOr f_has = f_doc.HasField(key, handle); + EXPECT_EQ(s_has.ok(), f_has.ok()); + if (s_has.ok() && f_has.ok()) { + EXPECT_EQ(*s_has, *f_has); + } + } + } else if (s_doc.IsArray()) { + absl::StatusOr> s_arr = s_doc.GetArray(); + absl::StatusOr> f_arr = + f_doc.GetArray(handle); + ASSERT_EQ(s_arr.ok(), f_arr.ok()); + if (!s_arr.ok() || !f_arr.ok()) return; + ASSERT_EQ(s_arr->size(), f_arr->size()); + + for (size_t i = 0; i < s_arr->size(); ++i) { + WalkAndCompare((*s_arr)[i], f_doc, (*f_arr)[i]); + + absl::StatusOr s_elem = s_doc.GetArrayElement(i); + absl::StatusOr f_elem = + f_doc.GetArrayElement(i, handle); + EXPECT_EQ(s_elem.ok(), f_elem.ok()); + if (s_elem.ok() && f_elem.ok()) { + WalkAndCompare(*s_elem, f_doc, *f_elem); + } + } + } +} + +void CompareSerdeJsonAndFastSerdeJson(absl::string_view json_str) { + absl::StatusOr s_doc = SerdeJson::Parse(json_str); + absl::StatusOr f_doc = FastSerdeJson::Parse(json_str); + + ASSERT_EQ(s_doc.ok(), f_doc.ok()); + if (!s_doc.ok()) { + return; + } + + WalkAndCompare(*s_doc, *f_doc); +} +FUZZ_TEST(SerdeDifferentialFuzzer, CompareSerdeJsonAndFastSerdeJson); + +} // namespace +} // namespace security::json