From 380eb750ece1395c24f720b353a9ed028c1303ac Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Wed, 29 Jan 2020 14:52:19 -0800 Subject: [PATCH] New JSON API --- BUILD | 2 + BUILD.gn | 2 + CMakeLists.txt | 49 ++ Makefile | 60 ++ build.yaml | 13 + config.m4 | 2 + config.w32 | 2 + gRPC-Core.podspec | 2 + grpc.gemspec | 2 + grpc.gyp | 10 + package.xml | 2 + src/core/lib/json/json.h | 213 +++++- src/core/lib/json/json_reader_new.cc | 808 ++++++++++++++++++++ src/core/lib/json/json_writer_new.cc | 336 ++++++++ src/python/grpcio/grpc_core_dependencies.py | 2 + test/core/json/BUILD | 15 + test/core/json/json_test_new.cc | 295 +++++++ tools/doxygen/Doxyfile.core.internal | 2 + tools/run_tests/generated/tests.json | 24 + 19 files changed, 1840 insertions(+), 1 deletion(-) create mode 100644 src/core/lib/json/json_reader_new.cc create mode 100644 src/core/lib/json/json_writer_new.cc create mode 100644 test/core/json/json_test_new.cc diff --git a/BUILD b/BUILD index b7e50c1b411..e4d4b3eddce 100644 --- a/BUILD +++ b/BUILD @@ -806,7 +806,9 @@ grpc_cc_library( "src/core/lib/iomgr/wakeup_fd_posix.cc", "src/core/lib/json/json.cc", "src/core/lib/json/json_reader.cc", + "src/core/lib/json/json_reader_new.cc", "src/core/lib/json/json_writer.cc", + "src/core/lib/json/json_writer_new.cc", "src/core/lib/slice/b64.cc", "src/core/lib/slice/percent_encoding.cc", "src/core/lib/slice/slice.cc", diff --git a/BUILD.gn b/BUILD.gn index bc4b82154cd..8d746fc881e 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -718,7 +718,9 @@ config("grpc_config") { "src/core/lib/json/json.cc", "src/core/lib/json/json.h", "src/core/lib/json/json_reader.cc", + "src/core/lib/json/json_reader_new.cc", "src/core/lib/json/json_writer.cc", + "src/core/lib/json/json_writer_new.cc", "src/core/lib/security/context/security_context.cc", "src/core/lib/security/context/security_context.h", "src/core/lib/security/credentials/alts/alts_credentials.cc", diff --git a/CMakeLists.txt b/CMakeLists.txt index 2da58394f0e..5546b3d35a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -826,6 +826,7 @@ if(gRPC_BUILD_TESTS) if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) add_dependencies(buildtests_cxx json_run_localhost) endif() + add_dependencies(buildtests_cxx json_test_new) add_dependencies(buildtests_cxx logical_thread_test) add_dependencies(buildtests_cxx message_allocator_end2end_test) add_dependencies(buildtests_cxx metrics_client) @@ -1125,7 +1126,9 @@ add_library(alts_test_util src/core/lib/iomgr/wakeup_fd_posix.cc src/core/lib/json/json.cc src/core/lib/json/json_reader.cc + src/core/lib/json/json_reader_new.cc src/core/lib/json/json_writer.cc + src/core/lib/json/json_writer_new.cc src/core/lib/slice/b64.cc src/core/lib/slice/percent_encoding.cc src/core/lib/slice/slice.cc @@ -1613,7 +1616,9 @@ add_library(grpc src/core/lib/iomgr/wakeup_fd_posix.cc src/core/lib/json/json.cc src/core/lib/json/json_reader.cc + src/core/lib/json/json_reader_new.cc src/core/lib/json/json_writer.cc + src/core/lib/json/json_writer_new.cc src/core/lib/slice/b64.cc src/core/lib/slice/percent_encoding.cc src/core/lib/slice/slice.cc @@ -2097,7 +2102,9 @@ add_library(grpc_cronet src/core/lib/iomgr/wakeup_fd_posix.cc src/core/lib/json/json.cc src/core/lib/json/json_reader.cc + src/core/lib/json/json_reader_new.cc src/core/lib/json/json_writer.cc + src/core/lib/json/json_writer_new.cc src/core/lib/slice/b64.cc src/core/lib/slice/percent_encoding.cc src/core/lib/slice/slice.cc @@ -2526,7 +2533,9 @@ add_library(grpc_test_util src/core/lib/iomgr/wakeup_fd_posix.cc src/core/lib/json/json.cc src/core/lib/json/json_reader.cc + src/core/lib/json/json_reader_new.cc src/core/lib/json/json_writer.cc + src/core/lib/json/json_writer_new.cc src/core/lib/slice/b64.cc src/core/lib/slice/percent_encoding.cc src/core/lib/slice/slice.cc @@ -2869,7 +2878,9 @@ add_library(grpc_test_util_unsecure src/core/lib/iomgr/wakeup_fd_posix.cc src/core/lib/json/json.cc src/core/lib/json/json_reader.cc + src/core/lib/json/json_reader_new.cc src/core/lib/json/json_writer.cc + src/core/lib/json/json_writer_new.cc src/core/lib/slice/b64.cc src/core/lib/slice/percent_encoding.cc src/core/lib/slice/slice.cc @@ -3188,7 +3199,9 @@ add_library(grpc_unsecure src/core/lib/iomgr/wakeup_fd_posix.cc src/core/lib/json/json.cc src/core/lib/json/json_reader.cc + src/core/lib/json/json_reader_new.cc src/core/lib/json/json_writer.cc + src/core/lib/json/json_writer_new.cc src/core/lib/slice/b64.cc src/core/lib/slice/percent_encoding.cc src/core/lib/slice/slice.cc @@ -14004,6 +14017,42 @@ endif() endif() if(gRPC_BUILD_TESTS) +add_executable(json_test_new + test/core/json/json_test_new.cc + third_party/googletest/googletest/src/gtest-all.cc + third_party/googletest/googlemock/src/gmock-all.cc +) + +target_include_directories(json_test_new + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR} + ${_gRPC_SSL_INCLUDE_DIR} + ${_gRPC_UPB_GENERATED_DIR} + ${_gRPC_UPB_GRPC_GENERATED_DIR} + ${_gRPC_UPB_INCLUDE_DIR} + ${_gRPC_ZLIB_INCLUDE_DIR} + third_party/googletest/googletest/include + third_party/googletest/googletest + third_party/googletest/googlemock/include + third_party/googletest/googlemock + ${_gRPC_PROTO_GENS_DIR} +) + +target_link_libraries(json_test_new + ${_gRPC_PROTOBUF_LIBRARIES} + ${_gRPC_ALLTARGETS_LIBRARIES} + grpc_test_util + grpc + gpr + ${_gRPC_GFLAGS_LIBRARIES} +) + + +endif() +if(gRPC_BUILD_TESTS) + add_executable(logical_thread_test test/core/iomgr/logical_thread_test.cc third_party/googletest/googletest/src/gtest-all.cc diff --git a/Makefile b/Makefile index 25fc9cd20e5..d528030d900 100644 --- a/Makefile +++ b/Makefile @@ -1256,6 +1256,7 @@ interop_client: $(BINDIR)/$(CONFIG)/interop_client interop_server: $(BINDIR)/$(CONFIG)/interop_server interop_test: $(BINDIR)/$(CONFIG)/interop_test json_run_localhost: $(BINDIR)/$(CONFIG)/json_run_localhost +json_test_new: $(BINDIR)/$(CONFIG)/json_test_new logical_thread_test: $(BINDIR)/$(CONFIG)/logical_thread_test message_allocator_end2end_test: $(BINDIR)/$(CONFIG)/message_allocator_end2end_test metrics_client: $(BINDIR)/$(CONFIG)/metrics_client @@ -1726,6 +1727,7 @@ buildtests_cxx: privatelibs_cxx \ $(BINDIR)/$(CONFIG)/interop_server \ $(BINDIR)/$(CONFIG)/interop_test \ $(BINDIR)/$(CONFIG)/json_run_localhost \ + $(BINDIR)/$(CONFIG)/json_test_new \ $(BINDIR)/$(CONFIG)/logical_thread_test \ $(BINDIR)/$(CONFIG)/message_allocator_end2end_test \ $(BINDIR)/$(CONFIG)/metrics_client \ @@ -1901,6 +1903,7 @@ buildtests_cxx: privatelibs_cxx \ $(BINDIR)/$(CONFIG)/interop_server \ $(BINDIR)/$(CONFIG)/interop_test \ $(BINDIR)/$(CONFIG)/json_run_localhost \ + $(BINDIR)/$(CONFIG)/json_test_new \ $(BINDIR)/$(CONFIG)/logical_thread_test \ $(BINDIR)/$(CONFIG)/message_allocator_end2end_test \ $(BINDIR)/$(CONFIG)/metrics_client \ @@ -2414,6 +2417,8 @@ test_cxx: buildtests_cxx $(Q) $(BINDIR)/$(CONFIG)/inproc_sync_unary_ping_pong_test || ( echo test inproc_sync_unary_ping_pong_test failed ; exit 1 ) $(E) "[RUN] Testing interop_test" $(Q) $(BINDIR)/$(CONFIG)/interop_test || ( echo test interop_test failed ; exit 1 ) + $(E) "[RUN] Testing json_test_new" + $(Q) $(BINDIR)/$(CONFIG)/json_test_new || ( echo test json_test_new failed ; exit 1 ) $(E) "[RUN] Testing logical_thread_test" $(Q) $(BINDIR)/$(CONFIG)/logical_thread_test || ( echo test logical_thread_test failed ; exit 1 ) $(E) "[RUN] Testing message_allocator_end2end_test" @@ -3635,7 +3640,9 @@ LIBALTS_TEST_UTIL_SRC = \ src/core/lib/iomgr/wakeup_fd_posix.cc \ src/core/lib/json/json.cc \ src/core/lib/json/json_reader.cc \ + src/core/lib/json/json_reader_new.cc \ src/core/lib/json/json_writer.cc \ + src/core/lib/json/json_writer_new.cc \ src/core/lib/slice/b64.cc \ src/core/lib/slice/percent_encoding.cc \ src/core/lib/slice/slice.cc \ @@ -4091,7 +4098,9 @@ LIBGRPC_SRC = \ src/core/lib/iomgr/wakeup_fd_posix.cc \ src/core/lib/json/json.cc \ src/core/lib/json/json_reader.cc \ + src/core/lib/json/json_reader_new.cc \ src/core/lib/json/json_writer.cc \ + src/core/lib/json/json_writer_new.cc \ src/core/lib/slice/b64.cc \ src/core/lib/slice/percent_encoding.cc \ src/core/lib/slice/slice.cc \ @@ -4567,7 +4576,9 @@ LIBGRPC_CRONET_SRC = \ src/core/lib/iomgr/wakeup_fd_posix.cc \ src/core/lib/json/json.cc \ src/core/lib/json/json_reader.cc \ + src/core/lib/json/json_reader_new.cc \ src/core/lib/json/json_writer.cc \ + src/core/lib/json/json_writer_new.cc \ src/core/lib/slice/b64.cc \ src/core/lib/slice/percent_encoding.cc \ src/core/lib/slice/slice.cc \ @@ -4987,7 +4998,9 @@ LIBGRPC_TEST_UTIL_SRC = \ src/core/lib/iomgr/wakeup_fd_posix.cc \ src/core/lib/json/json.cc \ src/core/lib/json/json_reader.cc \ + src/core/lib/json/json_reader_new.cc \ src/core/lib/json/json_writer.cc \ + src/core/lib/json/json_writer_new.cc \ src/core/lib/slice/b64.cc \ src/core/lib/slice/percent_encoding.cc \ src/core/lib/slice/slice.cc \ @@ -5316,7 +5329,9 @@ LIBGRPC_TEST_UTIL_UNSECURE_SRC = \ src/core/lib/iomgr/wakeup_fd_posix.cc \ src/core/lib/json/json.cc \ src/core/lib/json/json_reader.cc \ + src/core/lib/json/json_reader_new.cc \ src/core/lib/json/json_writer.cc \ + src/core/lib/json/json_writer_new.cc \ src/core/lib/slice/b64.cc \ src/core/lib/slice/percent_encoding.cc \ src/core/lib/slice/slice.cc \ @@ -5608,7 +5623,9 @@ LIBGRPC_UNSECURE_SRC = \ src/core/lib/iomgr/wakeup_fd_posix.cc \ src/core/lib/json/json.cc \ src/core/lib/json/json_reader.cc \ + src/core/lib/json/json_reader_new.cc \ src/core/lib/json/json_writer.cc \ + src/core/lib/json/json_writer_new.cc \ src/core/lib/slice/b64.cc \ src/core/lib/slice/percent_encoding.cc \ src/core/lib/slice/slice.cc \ @@ -18350,6 +18367,49 @@ endif endif +JSON_TEST_NEW_SRC = \ + test/core/json/json_test_new.cc \ + +JSON_TEST_NEW_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(JSON_TEST_NEW_SRC)))) +ifeq ($(NO_SECURE),true) + +# You can't build secure targets if you don't have OpenSSL. + +$(BINDIR)/$(CONFIG)/json_test_new: openssl_dep_error + +else + + + + +ifeq ($(NO_PROTOBUF),true) + +# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.5.0+. + +$(BINDIR)/$(CONFIG)/json_test_new: protobuf_dep_error + +else + +$(BINDIR)/$(CONFIG)/json_test_new: $(PROTOBUF_DEP) $(JSON_TEST_NEW_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a + $(E) "[LD] Linking $@" + $(Q) mkdir -p `dirname $@` + $(Q) $(LDXX) $(LDFLAGS) $(JSON_TEST_NEW_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/json_test_new + +endif + +endif + +$(OBJDIR)/$(CONFIG)/test/core/json/json_test_new.o: $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a + +deps_json_test_new: $(JSON_TEST_NEW_OBJS:.o=.dep) + +ifneq ($(NO_SECURE),true) +ifneq ($(NO_DEPS),true) +-include $(JSON_TEST_NEW_OBJS:.o=.dep) +endif +endif + + LOGICAL_THREAD_TEST_SRC = \ test/core/iomgr/logical_thread_test.cc \ diff --git a/build.yaml b/build.yaml index 2653961c4d9..f15e2eb4db8 100644 --- a/build.yaml +++ b/build.yaml @@ -772,7 +772,9 @@ filegroups: - src/core/lib/iomgr/wakeup_fd_posix.cc - src/core/lib/json/json.cc - src/core/lib/json/json_reader.cc + - src/core/lib/json/json_reader_new.cc - src/core/lib/json/json_writer.cc + - src/core/lib/json/json_writer_new.cc - src/core/lib/slice/b64.cc - src/core/lib/slice/percent_encoding.cc - src/core/lib/slice/slice.cc @@ -5393,6 +5395,17 @@ targets: - mac - linux - posix +- name: json_test_new + gtest: true + build: test + language: c++ + src: + - test/core/json/json_test_new.cc + deps: + - grpc_test_util + - grpc + - gpr + uses_polling: false - name: logical_thread_test cpu_cost: 10 build: test diff --git a/config.m4 b/config.m4 index eb6b8ab3fbb..2cd34f112b6 100644 --- a/config.m4 +++ b/config.m4 @@ -348,7 +348,9 @@ if test "$PHP_GRPC" != "no"; then src/core/lib/iomgr/wakeup_fd_posix.cc \ src/core/lib/json/json.cc \ src/core/lib/json/json_reader.cc \ + src/core/lib/json/json_reader_new.cc \ src/core/lib/json/json_writer.cc \ + src/core/lib/json/json_writer_new.cc \ src/core/lib/profiling/basic_timers.cc \ src/core/lib/profiling/stap_timers.cc \ src/core/lib/security/context/security_context.cc \ diff --git a/config.w32 b/config.w32 index 707bf3b69bd..9a2c352eb54 100644 --- a/config.w32 +++ b/config.w32 @@ -317,7 +317,9 @@ if (PHP_GRPC != "no") { "src\\core\\lib\\iomgr\\wakeup_fd_posix.cc " + "src\\core\\lib\\json\\json.cc " + "src\\core\\lib\\json\\json_reader.cc " + + "src\\core\\lib\\json\\json_reader_new.cc " + "src\\core\\lib\\json\\json_writer.cc " + + "src\\core\\lib\\json\\json_writer_new.cc " + "src\\core\\lib\\profiling\\basic_timers.cc " + "src\\core\\lib\\profiling\\stap_timers.cc " + "src\\core\\lib\\security\\context\\security_context.cc " + diff --git a/gRPC-Core.podspec b/gRPC-Core.podspec index 5c2073c0d3d..1b303e1abe6 100644 --- a/gRPC-Core.podspec +++ b/gRPC-Core.podspec @@ -753,7 +753,9 @@ Pod::Spec.new do |s| 'src/core/lib/json/json.cc', 'src/core/lib/json/json.h', 'src/core/lib/json/json_reader.cc', + 'src/core/lib/json/json_reader_new.cc', 'src/core/lib/json/json_writer.cc', + 'src/core/lib/json/json_writer_new.cc', 'src/core/lib/profiling/basic_timers.cc', 'src/core/lib/profiling/stap_timers.cc', 'src/core/lib/profiling/timers.h', diff --git a/grpc.gemspec b/grpc.gemspec index 632bdd73521..6ad29c6a8fa 100644 --- a/grpc.gemspec +++ b/grpc.gemspec @@ -676,7 +676,9 @@ Gem::Specification.new do |s| s.files += %w( src/core/lib/json/json.cc ) s.files += %w( src/core/lib/json/json.h ) s.files += %w( src/core/lib/json/json_reader.cc ) + s.files += %w( src/core/lib/json/json_reader_new.cc ) s.files += %w( src/core/lib/json/json_writer.cc ) + s.files += %w( src/core/lib/json/json_writer_new.cc ) s.files += %w( src/core/lib/profiling/basic_timers.cc ) s.files += %w( src/core/lib/profiling/stap_timers.cc ) s.files += %w( src/core/lib/profiling/timers.h ) diff --git a/grpc.gyp b/grpc.gyp index 92a168d50ac..b43ddf05c7a 100644 --- a/grpc.gyp +++ b/grpc.gyp @@ -319,7 +319,9 @@ 'src/core/lib/iomgr/wakeup_fd_posix.cc', 'src/core/lib/json/json.cc', 'src/core/lib/json/json_reader.cc', + 'src/core/lib/json/json_reader_new.cc', 'src/core/lib/json/json_writer.cc', + 'src/core/lib/json/json_writer_new.cc', 'src/core/lib/slice/b64.cc', 'src/core/lib/slice/percent_encoding.cc', 'src/core/lib/slice/slice.cc', @@ -617,7 +619,9 @@ 'src/core/lib/iomgr/wakeup_fd_posix.cc', 'src/core/lib/json/json.cc', 'src/core/lib/json/json_reader.cc', + 'src/core/lib/json/json_reader_new.cc', 'src/core/lib/json/json_writer.cc', + 'src/core/lib/json/json_writer_new.cc', 'src/core/lib/slice/b64.cc', 'src/core/lib/slice/percent_encoding.cc', 'src/core/lib/slice/slice.cc', @@ -1040,7 +1044,9 @@ 'src/core/lib/iomgr/wakeup_fd_posix.cc', 'src/core/lib/json/json.cc', 'src/core/lib/json/json_reader.cc', + 'src/core/lib/json/json_reader_new.cc', 'src/core/lib/json/json_writer.cc', + 'src/core/lib/json/json_writer_new.cc', 'src/core/lib/slice/b64.cc', 'src/core/lib/slice/percent_encoding.cc', 'src/core/lib/slice/slice.cc', @@ -1303,7 +1309,9 @@ 'src/core/lib/iomgr/wakeup_fd_posix.cc', 'src/core/lib/json/json.cc', 'src/core/lib/json/json_reader.cc', + 'src/core/lib/json/json_reader_new.cc', 'src/core/lib/json/json_writer.cc', + 'src/core/lib/json/json_writer_new.cc', 'src/core/lib/slice/b64.cc', 'src/core/lib/slice/percent_encoding.cc', 'src/core/lib/slice/slice.cc', @@ -1542,7 +1550,9 @@ 'src/core/lib/iomgr/wakeup_fd_posix.cc', 'src/core/lib/json/json.cc', 'src/core/lib/json/json_reader.cc', + 'src/core/lib/json/json_reader_new.cc', 'src/core/lib/json/json_writer.cc', + 'src/core/lib/json/json_writer_new.cc', 'src/core/lib/slice/b64.cc', 'src/core/lib/slice/percent_encoding.cc', 'src/core/lib/slice/slice.cc', diff --git a/package.xml b/package.xml index 7fee466a057..7d80840edc4 100644 --- a/package.xml +++ b/package.xml @@ -659,7 +659,9 @@ + + diff --git a/src/core/lib/json/json.h b/src/core/lib/json/json.h index d7ac989026e..de3c2aa1a50 100644 --- a/src/core/lib/json/json.h +++ b/src/core/lib/json/json.h @@ -21,9 +21,220 @@ #include -#include #include +#include +#include +#include + +#include "src/core/lib/gprpp/string_view.h" +#include "src/core/lib/iomgr/error.h" + +namespace grpc_core { + +// A JSON value, which can be any one of object, array, string, +// number, true, false, or null. +class Json { + public: + // TODO(roth): Currently, numbers are stored internally as strings, + // which makes the API a bit cumbersome to use. When we have time, + // consider whether there's a better alternative (e.g., maybe storing + // each numeric type as the native C++ type and automatically converting + // to string as needed). + enum class Type { + JSON_NULL, + JSON_TRUE, + JSON_FALSE, + NUMBER, + STRING, + OBJECT, + ARRAY + }; + + using Object = std::map; + using Array = std::vector; + + // Parses JSON string from json_str. On error, sets *error. + static Json Parse(StringView json_str, grpc_error** error); + + Json() = default; + + // Copyable. + Json(const Json& other) { CopyFrom(other); } + Json& operator=(const Json& other) { + CopyFrom(other); + return *this; + } + + // Moveable. + Json(Json&& other) { MoveFrom(std::move(other)); } + Json& operator=(Json&& other) { + MoveFrom(std::move(other)); + return *this; + } + + // Construct from copying a string. + // If is_number is true, the type will be NUMBER instead of STRING. + Json(const std::string& string, bool is_number = false) + : type_(is_number ? Type::NUMBER : Type::STRING), string_value_(string) {} + Json& operator=(const std::string& string) { + type_ = Type::STRING; + string_value_ = string; + return *this; + } + + // Same thing for C-style strings, both const and mutable. + Json(const char* string, bool is_number = false) + : Json(std::string(string), is_number) {} + Json& operator=(const char* string) { + *this = std::string(string); + return *this; + } + Json(char* string, bool is_number = false) + : Json(std::string(string), is_number) {} + Json& operator=(char* string) { + *this = std::string(string); + return *this; + } + + // Construct by moving a string. + Json(std::string&& string) + : type_(Type::STRING), string_value_(std::move(string)) {} + Json& operator=(std::string&& string) { + type_ = Type::STRING; + string_value_ = std::move(string); + return *this; + } + + // Construct from bool. + Json(bool b) : type_(b ? Type::JSON_TRUE : Type::JSON_FALSE) {} + Json& operator=(bool b) { + type_ = b ? Type::JSON_TRUE : Type::JSON_FALSE; + return *this; + } + + // Construct from any numeric type. + template + Json(NumericType number) + : type_(Type::NUMBER), string_value_(std::to_string(number)) {} + template + Json& operator=(NumericType number) { + type_ = Type::NUMBER; + string_value_ = std::to_string(number); + return *this; + } + + // Construct by copying object. + Json(const Object& object) : type_(Type::OBJECT), object_value_(object) {} + Json& operator=(const Object& object) { + type_ = Type::OBJECT; + object_value_ = object; + return *this; + } + + // Construct by moving object. + Json(Object&& object) + : type_(Type::OBJECT), object_value_(std::move(object)) {} + Json& operator=(Object&& object) { + type_ = Type::OBJECT; + object_value_ = std::move(object); + return *this; + } + + // Construct by copying array. + Json(const Array& array) : type_(Type::ARRAY), array_value_(array) {} + Json& operator=(const Array& array) { + type_ = Type::ARRAY; + array_value_ = array; + return *this; + } + + // Construct by moving array. + Json(Array&& array) : type_(Type::ARRAY), array_value_(std::move(array)) {} + Json& operator=(Array&& array) { + type_ = Type::ARRAY; + array_value_ = std::move(array); + return *this; + } + + // Dumps JSON from value to string form. + std::string Dump(int indent = 0) const; + + // Accessor methods. + Type type() const { return type_; } + const std::string& string_value() const { return string_value_; } + const Object& object_value() const { return object_value_; } + Object* mutable_object() { return &object_value_; } + const Array& array_value() const { return array_value_; } + Array* mutable_array() { return &array_value_; } + + bool operator==(const Json& other) const { + if (type_ != other.type_) return false; + switch (type_) { + case Type::NUMBER: + case Type::STRING: + if (string_value_ != other.string_value_) return false; + break; + case Type::OBJECT: + if (object_value_ != other.object_value_) return false; + break; + case Type::ARRAY: + if (array_value_ != other.array_value_) return false; + break; + default: + break; + } + return true; + } + + bool operator!=(const Json& other) const { return !(*this == other); } + + private: + void CopyFrom(const Json& other) { + type_ = other.type_; + switch (type_) { + case Type::NUMBER: + case Type::STRING: + string_value_ = other.string_value_; + break; + case Type::OBJECT: + object_value_ = other.object_value_; + break; + case Type::ARRAY: + array_value_ = other.array_value_; + break; + default: + break; + } + } + + void MoveFrom(Json&& other) { + type_ = other.type_; + other.type_ = Type::JSON_NULL; + switch (type_) { + case Type::NUMBER: + case Type::STRING: + string_value_ = std::move(other.string_value_); + break; + case Type::OBJECT: + object_value_ = std::move(other.object_value_); + break; + case Type::ARRAY: + array_value_ = std::move(other.array_value_); + break; + default: + break; + } + } + + Type type_ = Type::JSON_NULL; + std::string string_value_; + Object object_value_; + Array array_value_; +}; + +} // namespace grpc_core + /* The various json types. */ typedef enum { GRPC_JSON_OBJECT, diff --git a/src/core/lib/json/json_reader_new.cc b/src/core/lib/json/json_reader_new.cc new file mode 100644 index 00000000000..acc8685efe9 --- /dev/null +++ b/src/core/lib/json/json_reader_new.cc @@ -0,0 +1,808 @@ +/* + * + * Copyright 2015-2016 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +#include + +#include + +#include "src/core/lib/json/json.h" + +namespace grpc_core { + +namespace { + +class JsonReader { + public: + enum class Status { + GRPC_JSON_DONE, /* The parser finished successfully. */ + GRPC_JSON_PARSE_ERROR, /* The parser found an error in the json stream. */ + GRPC_JSON_INTERNAL_ERROR /* The parser got an internal error. */ + }; + + static Status Parse(StringView input, Json* output); + + private: + enum class State { + GRPC_JSON_STATE_OBJECT_KEY_BEGIN, + GRPC_JSON_STATE_OBJECT_KEY_STRING, + GRPC_JSON_STATE_OBJECT_KEY_END, + GRPC_JSON_STATE_VALUE_BEGIN, + GRPC_JSON_STATE_VALUE_STRING, + GRPC_JSON_STATE_STRING_ESCAPE, + GRPC_JSON_STATE_STRING_ESCAPE_U1, + GRPC_JSON_STATE_STRING_ESCAPE_U2, + GRPC_JSON_STATE_STRING_ESCAPE_U3, + GRPC_JSON_STATE_STRING_ESCAPE_U4, + GRPC_JSON_STATE_VALUE_NUMBER, + GRPC_JSON_STATE_VALUE_NUMBER_WITH_DECIMAL, + GRPC_JSON_STATE_VALUE_NUMBER_ZERO, + GRPC_JSON_STATE_VALUE_NUMBER_DOT, + GRPC_JSON_STATE_VALUE_NUMBER_E, + GRPC_JSON_STATE_VALUE_NUMBER_EPM, + GRPC_JSON_STATE_VALUE_TRUE_R, + GRPC_JSON_STATE_VALUE_TRUE_U, + GRPC_JSON_STATE_VALUE_TRUE_E, + GRPC_JSON_STATE_VALUE_FALSE_A, + GRPC_JSON_STATE_VALUE_FALSE_L, + GRPC_JSON_STATE_VALUE_FALSE_S, + GRPC_JSON_STATE_VALUE_FALSE_E, + GRPC_JSON_STATE_VALUE_NULL_U, + GRPC_JSON_STATE_VALUE_NULL_L1, + GRPC_JSON_STATE_VALUE_NULL_L2, + GRPC_JSON_STATE_VALUE_END, + GRPC_JSON_STATE_END + }; + + /* The first non-unicode value is 0x110000. But let's pick + * a value high enough to start our error codes from. These + * values are safe to return from the read_char function. + */ + static constexpr uint32_t GRPC_JSON_READ_CHAR_EOF = 0x7ffffff0; + + explicit JsonReader(StringView input) + : input_(reinterpret_cast(input.data())), + remaining_input_(input.size()) {} + + Status Run(); + uint32_t ReadChar(); + bool IsComplete(); + + void StringAddChar(uint32_t c); + void StringAddUtf32(uint32_t c); + + Json* CreateAndLinkValue(); + void StartContainer(Json::Type type); + void EndContainer(); + void SetKey(); + void SetString(); + bool SetNumber(); + void SetTrue(); + void SetFalse(); + void SetNull(); + + const uint8_t* input_; + size_t remaining_input_; + + State state_ = State::GRPC_JSON_STATE_VALUE_BEGIN; + bool escaped_string_was_key_ = false; + bool container_just_begun_ = false; + uint16_t unicode_char_ = 0; + uint16_t unicode_high_surrogate_ = 0; + bool duplicate_key_found_ = false; + + Json root_value_; + std::vector stack_; + + std::string key_; + std::string string_; +}; + +void JsonReader::StringAddChar(uint32_t c) { + string_.push_back(static_cast(c)); +} + +void JsonReader::StringAddUtf32(uint32_t c) { + if (c <= 0x7f) { + StringAddChar(c); + } else if (c <= 0x7ff) { + uint32_t b1 = 0xc0 | ((c >> 6) & 0x1f); + uint32_t b2 = 0x80 | (c & 0x3f); + StringAddChar(b1); + StringAddChar(b2); + } else if (c <= 0xffff) { + uint32_t b1 = 0xe0 | ((c >> 12) & 0x0f); + uint32_t b2 = 0x80 | ((c >> 6) & 0x3f); + uint32_t b3 = 0x80 | (c & 0x3f); + StringAddChar(b1); + StringAddChar(b2); + StringAddChar(b3); + } else if (c <= 0x1fffff) { + uint32_t b1 = 0xf0 | ((c >> 18) & 0x07); + uint32_t b2 = 0x80 | ((c >> 12) & 0x3f); + uint32_t b3 = 0x80 | ((c >> 6) & 0x3f); + uint32_t b4 = 0x80 | (c & 0x3f); + StringAddChar(b1); + StringAddChar(b2); + StringAddChar(b3); + StringAddChar(b4); + } +} + +uint32_t JsonReader::ReadChar() { + if (remaining_input_ == 0) return GRPC_JSON_READ_CHAR_EOF; + const uint32_t r = *input_++; + --remaining_input_; + if (r == 0) { + remaining_input_ = 0; + return GRPC_JSON_READ_CHAR_EOF; + } + return r; +} + +Json* JsonReader::CreateAndLinkValue() { + Json* value; + if (stack_.empty()) { + value = &root_value_; + } else { + Json* parent = stack_.back(); + if (parent->type() == Json::Type::OBJECT) { + if (parent->object_value().find(key_) != parent->object_value().end()) { + duplicate_key_found_ = true; + } + value = &(*parent->mutable_object())[std::move(key_)]; + } else { + GPR_ASSERT(parent->type() == Json::Type::ARRAY); + parent->mutable_array()->emplace_back(); + value = &parent->mutable_array()->back(); + } + } + return value; +} + +void JsonReader::StartContainer(Json::Type type) { + Json* value = CreateAndLinkValue(); + if (type == Json::Type::OBJECT) { + *value = Json::Object(); + } else { + GPR_ASSERT(type == Json::Type::ARRAY); + *value = Json::Array(); + } + stack_.push_back(value); +} + +void JsonReader::EndContainer() { + GPR_ASSERT(!stack_.empty()); + stack_.pop_back(); +} + +void JsonReader::SetKey() { + key_ = std::move(string_); + string_.clear(); +} + +void JsonReader::SetString() { + Json* value = CreateAndLinkValue(); + *value = std::move(string_); + string_.clear(); +} + +bool JsonReader::SetNumber() { + Json* value = CreateAndLinkValue(); + *value = Json(std::move(string_), /*is_number=*/true); + string_.clear(); + return true; +} + +void JsonReader::SetTrue() { + Json* value = CreateAndLinkValue(); + *value = true; + string_.clear(); +} + +void JsonReader::SetFalse() { + Json* value = CreateAndLinkValue(); + *value = false; + string_.clear(); +} + +void JsonReader::SetNull() { CreateAndLinkValue(); } + +bool JsonReader::IsComplete() { + return (stack_.empty() && (state_ == State::GRPC_JSON_STATE_END || + state_ == State::GRPC_JSON_STATE_VALUE_END)); +} + +/* Call this function to start parsing the input. It will return the following: + * . GRPC_JSON_DONE if the input got eof, and the parsing finished + * successfully. + * . GRPC_JSON_PARSE_ERROR if the input was somehow invalid. + * . GRPC_JSON_INTERNAL_ERROR if the parser somehow ended into an invalid + * internal state. + */ +JsonReader::Status JsonReader::Run() { + uint32_t c; + + /* This state-machine is a strict implementation of ECMA-404 */ + while (true) { + c = ReadChar(); + switch (c) { + /* Let's process the error case first. */ + case GRPC_JSON_READ_CHAR_EOF: + if (IsComplete()) { + return Status::GRPC_JSON_DONE; + } else { + return Status::GRPC_JSON_PARSE_ERROR; + } + break; + + /* Processing whitespaces. */ + case ' ': + case '\t': + case '\n': + case '\r': + switch (state_) { + case State::GRPC_JSON_STATE_OBJECT_KEY_BEGIN: + case State::GRPC_JSON_STATE_OBJECT_KEY_END: + case State::GRPC_JSON_STATE_VALUE_BEGIN: + case State::GRPC_JSON_STATE_VALUE_END: + case State::GRPC_JSON_STATE_END: + break; + + case State::GRPC_JSON_STATE_OBJECT_KEY_STRING: + case State::GRPC_JSON_STATE_VALUE_STRING: + if (c != ' ') return Status::GRPC_JSON_PARSE_ERROR; + if (unicode_high_surrogate_ != 0) { + return Status::GRPC_JSON_PARSE_ERROR; + } + StringAddChar(c); + break; + + case State::GRPC_JSON_STATE_VALUE_NUMBER: + case State::GRPC_JSON_STATE_VALUE_NUMBER_WITH_DECIMAL: + case State::GRPC_JSON_STATE_VALUE_NUMBER_ZERO: + case State::GRPC_JSON_STATE_VALUE_NUMBER_EPM: + if (!SetNumber()) return Status::GRPC_JSON_PARSE_ERROR; + state_ = State::GRPC_JSON_STATE_VALUE_END; + break; + + default: + return Status::GRPC_JSON_PARSE_ERROR; + } + break; + + /* Value, object or array terminations. */ + case ',': + case '}': + case ']': + switch (state_) { + case State::GRPC_JSON_STATE_OBJECT_KEY_STRING: + case State::GRPC_JSON_STATE_VALUE_STRING: + if (unicode_high_surrogate_ != 0) { + return Status::GRPC_JSON_PARSE_ERROR; + } + StringAddChar(c); + break; + + case State::GRPC_JSON_STATE_VALUE_NUMBER: + case State::GRPC_JSON_STATE_VALUE_NUMBER_WITH_DECIMAL: + case State::GRPC_JSON_STATE_VALUE_NUMBER_ZERO: + case State::GRPC_JSON_STATE_VALUE_NUMBER_EPM: + if (stack_.empty()) { + return Status::GRPC_JSON_PARSE_ERROR; + } else if (c == '}' && + stack_.back()->type() != Json::Type::OBJECT) { + return Status::GRPC_JSON_PARSE_ERROR; + return Status::GRPC_JSON_PARSE_ERROR; + } else if (c == ']' && stack_.back()->type() != Json::Type::ARRAY) { + return Status::GRPC_JSON_PARSE_ERROR; + } + if (!SetNumber()) return Status::GRPC_JSON_PARSE_ERROR; + state_ = State::GRPC_JSON_STATE_VALUE_END; + /* The missing break here is intentional. */ + /* fallthrough */ + + case State::GRPC_JSON_STATE_VALUE_END: + case State::GRPC_JSON_STATE_OBJECT_KEY_BEGIN: + case State::GRPC_JSON_STATE_VALUE_BEGIN: + if (c == ',') { + if (state_ != State::GRPC_JSON_STATE_VALUE_END) { + return Status::GRPC_JSON_PARSE_ERROR; + } + if (!stack_.empty() && + stack_.back()->type() == Json::Type::OBJECT) { + state_ = State::GRPC_JSON_STATE_OBJECT_KEY_BEGIN; + } else if (!stack_.empty() && + stack_.back()->type() == Json::Type::ARRAY) { + state_ = State::GRPC_JSON_STATE_VALUE_BEGIN; + } else { + return Status::GRPC_JSON_PARSE_ERROR; + } + } else { + if (stack_.empty()) { + return Status::GRPC_JSON_PARSE_ERROR; + } + if (c == '}' && stack_.back()->type() != Json::Type::OBJECT) { + return Status::GRPC_JSON_PARSE_ERROR; + } + if (c == '}' && + state_ == State::GRPC_JSON_STATE_OBJECT_KEY_BEGIN && + !container_just_begun_) { + return Status::GRPC_JSON_PARSE_ERROR; + } + if (c == ']' && stack_.back()->type() != Json::Type::ARRAY) { + return Status::GRPC_JSON_PARSE_ERROR; + } + if (c == ']' && state_ == State::GRPC_JSON_STATE_VALUE_BEGIN && + !container_just_begun_) { + return Status::GRPC_JSON_PARSE_ERROR; + } + state_ = State::GRPC_JSON_STATE_VALUE_END; + EndContainer(); + if (stack_.empty()) { + state_ = State::GRPC_JSON_STATE_END; + } + } + break; + + default: + return Status::GRPC_JSON_PARSE_ERROR; + } + break; + + /* In-string escaping. */ + case '\\': + switch (state_) { + case State::GRPC_JSON_STATE_OBJECT_KEY_STRING: + escaped_string_was_key_ = true; + state_ = State::GRPC_JSON_STATE_STRING_ESCAPE; + break; + + case State::GRPC_JSON_STATE_VALUE_STRING: + escaped_string_was_key_ = false; + state_ = State::GRPC_JSON_STATE_STRING_ESCAPE; + break; + + /* This is the \\ case. */ + case State::GRPC_JSON_STATE_STRING_ESCAPE: + if (unicode_high_surrogate_ != 0) + return Status::GRPC_JSON_PARSE_ERROR; + StringAddChar('\\'); + if (escaped_string_was_key_) { + state_ = State::GRPC_JSON_STATE_OBJECT_KEY_STRING; + } else { + state_ = State::GRPC_JSON_STATE_VALUE_STRING; + } + break; + + default: + return Status::GRPC_JSON_PARSE_ERROR; + } + break; + + default: + container_just_begun_ = false; + switch (state_) { + case State::GRPC_JSON_STATE_OBJECT_KEY_BEGIN: + if (c != '"') return Status::GRPC_JSON_PARSE_ERROR; + state_ = State::GRPC_JSON_STATE_OBJECT_KEY_STRING; + break; + + case State::GRPC_JSON_STATE_OBJECT_KEY_STRING: + if (unicode_high_surrogate_ != 0) { + return Status::GRPC_JSON_PARSE_ERROR; + } + if (c == '"') { + state_ = State::GRPC_JSON_STATE_OBJECT_KEY_END; + SetKey(); + } else { + if (c < 32) return Status::GRPC_JSON_PARSE_ERROR; + StringAddChar(c); + } + break; + + case State::GRPC_JSON_STATE_VALUE_STRING: + if (unicode_high_surrogate_ != 0) { + return Status::GRPC_JSON_PARSE_ERROR; + } + if (c == '"') { + state_ = State::GRPC_JSON_STATE_VALUE_END; + SetString(); + } else { + if (c < 32) return Status::GRPC_JSON_PARSE_ERROR; + StringAddChar(c); + } + break; + + case State::GRPC_JSON_STATE_OBJECT_KEY_END: + if (c != ':') return Status::GRPC_JSON_PARSE_ERROR; + state_ = State::GRPC_JSON_STATE_VALUE_BEGIN; + break; + + case State::GRPC_JSON_STATE_VALUE_BEGIN: + switch (c) { + case 't': + state_ = State::GRPC_JSON_STATE_VALUE_TRUE_R; + break; + + case 'f': + state_ = State::GRPC_JSON_STATE_VALUE_FALSE_A; + break; + + case 'n': + state_ = State::GRPC_JSON_STATE_VALUE_NULL_U; + break; + + case '"': + state_ = State::GRPC_JSON_STATE_VALUE_STRING; + break; + + case '0': + StringAddChar(c); + state_ = State::GRPC_JSON_STATE_VALUE_NUMBER_ZERO; + break; + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + StringAddChar(c); + state_ = State::GRPC_JSON_STATE_VALUE_NUMBER; + break; + + case '{': + container_just_begun_ = true; + StartContainer(Json::Type::OBJECT); + state_ = State::GRPC_JSON_STATE_OBJECT_KEY_BEGIN; + break; + + case '[': + container_just_begun_ = true; + StartContainer(Json::Type::ARRAY); + break; + default: + return Status::GRPC_JSON_PARSE_ERROR; + } + break; + + case State::GRPC_JSON_STATE_STRING_ESCAPE: + if (escaped_string_was_key_) { + state_ = State::GRPC_JSON_STATE_OBJECT_KEY_STRING; + } else { + state_ = State::GRPC_JSON_STATE_VALUE_STRING; + } + if (unicode_high_surrogate_ && c != 'u') { + return Status::GRPC_JSON_PARSE_ERROR; + } + switch (c) { + case '"': + case '/': + StringAddChar(c); + break; + case 'b': + StringAddChar('\b'); + break; + case 'f': + StringAddChar('\f'); + break; + case 'n': + StringAddChar('\n'); + break; + case 'r': + StringAddChar('\r'); + break; + case 't': + StringAddChar('\t'); + break; + case 'u': + state_ = State::GRPC_JSON_STATE_STRING_ESCAPE_U1; + unicode_char_ = 0; + break; + default: + return Status::GRPC_JSON_PARSE_ERROR; + } + break; + + case State::GRPC_JSON_STATE_STRING_ESCAPE_U1: + case State::GRPC_JSON_STATE_STRING_ESCAPE_U2: + case State::GRPC_JSON_STATE_STRING_ESCAPE_U3: + case State::GRPC_JSON_STATE_STRING_ESCAPE_U4: + if ((c >= '0') && (c <= '9')) { + c -= '0'; + } else if ((c >= 'A') && (c <= 'F')) { + c -= 'A' - 10; + } else if ((c >= 'a') && (c <= 'f')) { + c -= 'a' - 10; + } else { + return Status::GRPC_JSON_PARSE_ERROR; + } + unicode_char_ = static_cast(unicode_char_ << 4); + unicode_char_ = static_cast(unicode_char_ | c); + + switch (state_) { + case State::GRPC_JSON_STATE_STRING_ESCAPE_U1: + state_ = State::GRPC_JSON_STATE_STRING_ESCAPE_U2; + break; + case State::GRPC_JSON_STATE_STRING_ESCAPE_U2: + state_ = State::GRPC_JSON_STATE_STRING_ESCAPE_U3; + break; + case State::GRPC_JSON_STATE_STRING_ESCAPE_U3: + state_ = State::GRPC_JSON_STATE_STRING_ESCAPE_U4; + break; + case State::GRPC_JSON_STATE_STRING_ESCAPE_U4: + /* See grpc_json_writer_escape_string to have a description + * of what's going on here. + */ + if ((unicode_char_ & 0xfc00) == 0xd800) { + /* high surrogate utf-16 */ + if (unicode_high_surrogate_ != 0) + return Status::GRPC_JSON_PARSE_ERROR; + unicode_high_surrogate_ = unicode_char_; + } else if ((unicode_char_ & 0xfc00) == 0xdc00) { + /* low surrogate utf-16 */ + uint32_t utf32; + if (unicode_high_surrogate_ == 0) + return Status::GRPC_JSON_PARSE_ERROR; + utf32 = 0x10000; + utf32 += static_cast( + (unicode_high_surrogate_ - 0xd800) * 0x400); + utf32 += static_cast(unicode_char_ - 0xdc00); + StringAddUtf32(utf32); + unicode_high_surrogate_ = 0; + } else { + /* anything else */ + if (unicode_high_surrogate_ != 0) + return Status::GRPC_JSON_PARSE_ERROR; + StringAddUtf32(unicode_char_); + } + if (escaped_string_was_key_) { + state_ = State::GRPC_JSON_STATE_OBJECT_KEY_STRING; + } else { + state_ = State::GRPC_JSON_STATE_VALUE_STRING; + } + break; + default: + GPR_UNREACHABLE_CODE(return Status::GRPC_JSON_INTERNAL_ERROR); + } + break; + + case State::GRPC_JSON_STATE_VALUE_NUMBER: + StringAddChar(c); + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + break; + case 'e': + case 'E': + state_ = State::GRPC_JSON_STATE_VALUE_NUMBER_E; + break; + case '.': + state_ = State::GRPC_JSON_STATE_VALUE_NUMBER_DOT; + break; + default: + return Status::GRPC_JSON_PARSE_ERROR; + } + break; + + case State::GRPC_JSON_STATE_VALUE_NUMBER_WITH_DECIMAL: + StringAddChar(c); + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + break; + case 'e': + case 'E': + state_ = State::GRPC_JSON_STATE_VALUE_NUMBER_E; + break; + default: + return Status::GRPC_JSON_PARSE_ERROR; + } + break; + + case State::GRPC_JSON_STATE_VALUE_NUMBER_ZERO: + if (c != '.') return Status::GRPC_JSON_PARSE_ERROR; + StringAddChar(c); + state_ = State::GRPC_JSON_STATE_VALUE_NUMBER_DOT; + break; + + case State::GRPC_JSON_STATE_VALUE_NUMBER_DOT: + StringAddChar(c); + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + state_ = State::GRPC_JSON_STATE_VALUE_NUMBER_WITH_DECIMAL; + break; + default: + return Status::GRPC_JSON_PARSE_ERROR; + } + break; + + case State::GRPC_JSON_STATE_VALUE_NUMBER_E: + StringAddChar(c); + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + state_ = State::GRPC_JSON_STATE_VALUE_NUMBER_EPM; + break; + default: + return Status::GRPC_JSON_PARSE_ERROR; + } + break; + + case State::GRPC_JSON_STATE_VALUE_NUMBER_EPM: + StringAddChar(c); + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + break; + default: + return Status::GRPC_JSON_PARSE_ERROR; + } + break; + + case State::GRPC_JSON_STATE_VALUE_TRUE_R: + if (c != 'r') return Status::GRPC_JSON_PARSE_ERROR; + state_ = State::GRPC_JSON_STATE_VALUE_TRUE_U; + break; + + case State::GRPC_JSON_STATE_VALUE_TRUE_U: + if (c != 'u') return Status::GRPC_JSON_PARSE_ERROR; + state_ = State::GRPC_JSON_STATE_VALUE_TRUE_E; + break; + + case State::GRPC_JSON_STATE_VALUE_TRUE_E: + if (c != 'e') return Status::GRPC_JSON_PARSE_ERROR; + SetTrue(); + state_ = State::GRPC_JSON_STATE_VALUE_END; + break; + + case State::GRPC_JSON_STATE_VALUE_FALSE_A: + if (c != 'a') return Status::GRPC_JSON_PARSE_ERROR; + state_ = State::GRPC_JSON_STATE_VALUE_FALSE_L; + break; + + case State::GRPC_JSON_STATE_VALUE_FALSE_L: + if (c != 'l') return Status::GRPC_JSON_PARSE_ERROR; + state_ = State::GRPC_JSON_STATE_VALUE_FALSE_S; + break; + + case State::GRPC_JSON_STATE_VALUE_FALSE_S: + if (c != 's') return Status::GRPC_JSON_PARSE_ERROR; + state_ = State::GRPC_JSON_STATE_VALUE_FALSE_E; + break; + + case State::GRPC_JSON_STATE_VALUE_FALSE_E: + if (c != 'e') return Status::GRPC_JSON_PARSE_ERROR; + SetFalse(); + state_ = State::GRPC_JSON_STATE_VALUE_END; + break; + + case State::GRPC_JSON_STATE_VALUE_NULL_U: + if (c != 'u') return Status::GRPC_JSON_PARSE_ERROR; + state_ = State::GRPC_JSON_STATE_VALUE_NULL_L1; + break; + + case State::GRPC_JSON_STATE_VALUE_NULL_L1: + if (c != 'l') return Status::GRPC_JSON_PARSE_ERROR; + state_ = State::GRPC_JSON_STATE_VALUE_NULL_L2; + break; + + case State::GRPC_JSON_STATE_VALUE_NULL_L2: + if (c != 'l') return Status::GRPC_JSON_PARSE_ERROR; + SetNull(); + state_ = State::GRPC_JSON_STATE_VALUE_END; + break; + + /* All of the VALUE_END cases are handled in the specialized case + * above. */ + case State::GRPC_JSON_STATE_VALUE_END: + switch (c) { + case ',': + case '}': + case ']': + GPR_UNREACHABLE_CODE(return Status::GRPC_JSON_INTERNAL_ERROR); + break; + + default: + return Status::GRPC_JSON_PARSE_ERROR; + } + break; + + case State::GRPC_JSON_STATE_END: + return Status::GRPC_JSON_PARSE_ERROR; + } + } + } + + GPR_UNREACHABLE_CODE(return Status::GRPC_JSON_INTERNAL_ERROR); +} + +JsonReader::Status JsonReader::Parse(StringView input, Json* output) { + JsonReader reader(input); + Status status = reader.Run(); + if (reader.duplicate_key_found_) status = Status::GRPC_JSON_PARSE_ERROR; + if (status == Status::GRPC_JSON_DONE) { + *output = std::move(reader.root_value_); + } + return status; +} + +} // namespace + +Json Json::Parse(StringView json_str, grpc_error** error) { + Json value; + JsonReader::Status status = JsonReader::Parse(json_str, &value); + if (status == JsonReader::Status::GRPC_JSON_PARSE_ERROR) { + *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("JSON parse error"); + } else if (status == JsonReader::Status::GRPC_JSON_INTERNAL_ERROR) { + *error = + GRPC_ERROR_CREATE_FROM_STATIC_STRING("internal error in JSON parser"); + } + return value; +} + +} // namespace grpc_core diff --git a/src/core/lib/json/json_writer_new.cc b/src/core/lib/json/json_writer_new.cc new file mode 100644 index 00000000000..3f371902f88 --- /dev/null +++ b/src/core/lib/json/json_writer_new.cc @@ -0,0 +1,336 @@ +/* + * + * Copyright 2015 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +#include +#include + +#include +#include + +#include "src/core/lib/json/json.h" + +#include "src/core/lib/gprpp/string_view.h" + +namespace grpc_core { + +namespace { + +/* The idea of the writer is basically symmetrical of the reader. While the + * reader emits various calls to your code, the writer takes basically the + * same calls and emit json out of it. It doesn't try to make any check on + * the order of the calls you do on it. Meaning you can theorically force + * it to generate invalid json. + * + * Also, unlike the reader, the writer expects UTF-8 encoded input strings. + * These strings will be UTF-8 validated, and any invalid character will + * cut the conversion short, before any invalid UTF-8 sequence, thus forming + * a valid UTF-8 string overall. + */ +class JsonWriter { + public: + static std::string Dump(const Json& value, int indent); + + private: + explicit JsonWriter(int indent) : indent_(indent) {} + + void OutputCheck(size_t needed); + void OutputChar(char c); + void OutputString(const StringView str); + void OutputIndent(); + void ValueEnd(); + void EscapeUtf16(uint16_t utf16); + void EscapeString(const std::string& string); + void ContainerBegins(Json::Type type); + void ContainerEnds(Json::Type type); + void ObjectKey(const std::string& string); + void ValueRaw(const std::string& string); + void ValueString(const std::string& string); + + void DumpObject(const Json::Object& object); + void DumpArray(const Json::Array& array); + void DumpValue(const Json& value); + + int indent_; + int depth_ = 0; + bool container_empty_ = true; + bool got_key_ = false; + std::string output_; +}; + +/* This function checks if there's enough space left in the output buffer, + * and will enlarge it if necessary. We're only allocating chunks of 256 + * bytes at a time (or multiples thereof). + */ +void JsonWriter::OutputCheck(size_t needed) { + size_t free_space = output_.capacity() - output_.size(); + if (free_space >= needed) return; + needed -= free_space; + /* Round up by 256 bytes. */ + needed = (needed + 0xff) & ~0xffU; + output_.reserve(output_.capacity() + needed); +} + +void JsonWriter::OutputChar(char c) { + OutputCheck(1); + output_.push_back(c); +} + +void JsonWriter::OutputString(const StringView str) { + OutputCheck(str.size()); + output_.append(str.data(), str.size()); +} + +void JsonWriter::OutputIndent() { + static const char spacesstr[] = + " " + " " + " " + " "; + unsigned spaces = static_cast(depth_ * indent_); + if (indent_ == 0) return; + if (got_key_) { + OutputChar(' '); + return; + } + while (spaces >= (sizeof(spacesstr) - 1)) { + OutputString(StringView(spacesstr, sizeof(spacesstr) - 1)); + spaces -= static_cast(sizeof(spacesstr) - 1); + } + if (spaces == 0) return; + OutputString(StringView(spacesstr + sizeof(spacesstr) - 1 - spaces, spaces)); +} + +void JsonWriter::ValueEnd() { + if (container_empty_) { + container_empty_ = false; + if (indent_ == 0 || depth_ == 0) return; + OutputChar('\n'); + } else { + OutputChar(','); + if (indent_ == 0) return; + OutputChar('\n'); + } +} + +void JsonWriter::EscapeUtf16(uint16_t utf16) { + static const char hex[] = "0123456789abcdef"; + OutputString(StringView("\\u", 2)); + OutputChar(hex[(utf16 >> 12) & 0x0f]); + OutputChar(hex[(utf16 >> 8) & 0x0f]); + OutputChar(hex[(utf16 >> 4) & 0x0f]); + OutputChar(hex[(utf16)&0x0f]); +} + +void JsonWriter::EscapeString(const std::string& string) { + OutputChar('"'); + for (size_t idx = 0; idx < string.size(); ++idx) { + uint8_t c = static_cast(string[idx]); + if (c == 0) { + break; + } else if (c >= 32 && c <= 126) { + if (c == '\\' || c == '"') OutputChar('\\'); + OutputChar(static_cast(c)); + } else if (c < 32 || c == 127) { + switch (c) { + case '\b': + OutputString(StringView("\\b", 2)); + break; + case '\f': + OutputString(StringView("\\f", 2)); + break; + case '\n': + OutputString(StringView("\\n", 2)); + break; + case '\r': + OutputString(StringView("\\r", 2)); + break; + case '\t': + OutputString(StringView("\\t", 2)); + break; + default: + EscapeUtf16(c); + break; + } + } else { + uint32_t utf32 = 0; + int extra = 0; + int i; + int valid = 1; + if ((c & 0xe0) == 0xc0) { + utf32 = c & 0x1f; + extra = 1; + } else if ((c & 0xf0) == 0xe0) { + utf32 = c & 0x0f; + extra = 2; + } else if ((c & 0xf8) == 0xf0) { + utf32 = c & 0x07; + extra = 3; + } else { + break; + } + for (i = 0; i < extra; i++) { + utf32 <<= 6; + ++idx; + /* Breaks out and bail if we hit the end of the string. */ + if (idx == string.size()) { + valid = 0; + break; + } + c = static_cast(string[idx]); + /* Breaks out and bail on any invalid UTF-8 sequence, including \0. */ + if ((c & 0xc0) != 0x80) { + valid = 0; + break; + } + utf32 |= c & 0x3f; + } + if (!valid) break; + /* The range 0xd800 - 0xdfff is reserved by the surrogates ad vitam. + * Any other range is technically reserved for future usage, so if we + * don't want the software to break in the future, we have to allow + * anything else. The first non-unicode character is 0x110000. */ + if (((utf32 >= 0xd800) && (utf32 <= 0xdfff)) || (utf32 >= 0x110000)) + break; + if (utf32 >= 0x10000) { + /* If utf32 contains a character that is above 0xffff, it needs to be + * broken down into a utf-16 surrogate pair. A surrogate pair is first + * a high surrogate, followed by a low surrogate. Each surrogate holds + * 10 bits of usable data, thus allowing a total of 20 bits of data. + * The high surrogate marker is 0xd800, while the low surrogate marker + * is 0xdc00. The low 10 bits of each will be the usable data. + * + * After re-combining the 20 bits of data, one has to add 0x10000 to + * the resulting value, in order to obtain the original character. + * This is obviously because the range 0x0000 - 0xffff can be written + * without any special trick. + * + * Since 0x10ffff is the highest allowed character, we're working in + * the range 0x00000 - 0xfffff after we decrement it by 0x10000. + * That range is exactly 20 bits. + */ + utf32 -= 0x10000; + EscapeUtf16(static_cast(0xd800 | (utf32 >> 10))); + EscapeUtf16(static_cast(0xdc00 | (utf32 & 0x3ff))); + } else { + EscapeUtf16(static_cast(utf32)); + } + } + } + OutputChar('"'); +} + +void JsonWriter::ContainerBegins(Json::Type type) { + if (!got_key_) ValueEnd(); + OutputIndent(); + OutputChar(type == Json::Type::OBJECT ? '{' : '['); + container_empty_ = true; + got_key_ = false; + depth_++; +} + +void JsonWriter::ContainerEnds(Json::Type type) { + if (indent_ && !container_empty_) OutputChar('\n'); + depth_--; + if (!container_empty_) OutputIndent(); + OutputChar(type == Json::Type::OBJECT ? '}' : ']'); + container_empty_ = false; + got_key_ = false; +} + +void JsonWriter::ObjectKey(const std::string& string) { + ValueEnd(); + OutputIndent(); + EscapeString(string); + OutputChar(':'); + got_key_ = true; +} + +void JsonWriter::ValueRaw(const std::string& string) { + if (!got_key_) ValueEnd(); + OutputIndent(); + OutputString(string); + got_key_ = false; +} + +void JsonWriter::ValueString(const std::string& string) { + if (!got_key_) ValueEnd(); + OutputIndent(); + EscapeString(string); + got_key_ = false; +} + +void JsonWriter::DumpObject(const Json::Object& object) { + ContainerBegins(Json::Type::OBJECT); + for (const auto& p : object) { + ObjectKey(p.first.data()); + DumpValue(p.second); + } + ContainerEnds(Json::Type::OBJECT); +} + +void JsonWriter::DumpArray(const Json::Array& array) { + ContainerBegins(Json::Type::ARRAY); + for (const auto& v : array) { + DumpValue(v); + } + ContainerEnds(Json::Type::ARRAY); +} + +void JsonWriter::DumpValue(const Json& value) { + switch (value.type()) { + case Json::Type::OBJECT: + DumpObject(value.object_value()); + break; + case Json::Type::ARRAY: + DumpArray(value.array_value()); + break; + case Json::Type::STRING: + ValueString(value.string_value()); + break; + case Json::Type::NUMBER: + ValueRaw(value.string_value()); + break; + case Json::Type::JSON_TRUE: + ValueRaw(std::string("true", 4)); + break; + case Json::Type::JSON_FALSE: + ValueRaw(std::string("false", 5)); + break; + case Json::Type::JSON_NULL: + ValueRaw(std::string("null", 4)); + break; + default: + GPR_UNREACHABLE_CODE(abort()); + } +} + +std::string JsonWriter::Dump(const Json& value, int indent) { + JsonWriter writer(indent); + writer.DumpValue(value); + return std::move(writer.output_); +} + +} // namespace + +std::string Json::Dump(int indent) const { + return JsonWriter::Dump(*this, indent); +} + +} // namespace grpc_core diff --git a/src/python/grpcio/grpc_core_dependencies.py b/src/python/grpcio/grpc_core_dependencies.py index 65877087fe8..ff240bd9f07 100644 --- a/src/python/grpcio/grpc_core_dependencies.py +++ b/src/python/grpcio/grpc_core_dependencies.py @@ -326,7 +326,9 @@ CORE_SOURCE_FILES = [ 'src/core/lib/iomgr/wakeup_fd_posix.cc', 'src/core/lib/json/json.cc', 'src/core/lib/json/json_reader.cc', + 'src/core/lib/json/json_reader_new.cc', 'src/core/lib/json/json_writer.cc', + 'src/core/lib/json/json_writer_new.cc', 'src/core/lib/profiling/basic_timers.cc', 'src/core/lib/profiling/stap_timers.cc', 'src/core/lib/security/context/security_context.cc', diff --git a/test/core/json/BUILD b/test/core/json/BUILD index 45c1c412448..7cd920d0bd0 100644 --- a/test/core/json/BUILD +++ b/test/core/json/BUILD @@ -44,3 +44,18 @@ grpc_cc_test( "//test/core/util:grpc_test_util", ], ) + +grpc_cc_test( + name = "json_test_new", + srcs = ["json_test_new.cc"], + external_deps = [ + "gtest", + ], + language = "C++", + uses_polling = False, + deps = [ + "//:gpr", + "//:grpc", + "//test/core/util:grpc_test_util", + ], +) diff --git a/test/core/json/json_test_new.cc b/test/core/json/json_test_new.cc new file mode 100644 index 00000000000..1e5b404b94d --- /dev/null +++ b/test/core/json/json_test_new.cc @@ -0,0 +1,295 @@ +/* + * + * Copyright 2015-2016 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +#include +#include +#include + +#include +#include + +#include "src/core/lib/gpr/string.h" +#include "src/core/lib/gpr/useful.h" +#include "src/core/lib/json/json.h" + +#include "test/core/util/test_config.h" + +namespace grpc_core { + +void ValidateValue(const Json& actual, const Json& expected); + +void ValidateObject(const Json::Object& actual, const Json::Object& expected) { + ASSERT_EQ(actual.size(), expected.size()); + auto actual_it = actual.begin(); + for (const auto& p : expected) { + EXPECT_EQ(actual_it->first, p.first); + ValidateValue(actual_it->second, p.second); + ++actual_it; + } +} + +void ValidateArray(const Json::Array& actual, const Json::Array& expected) { + ASSERT_EQ(actual.size(), expected.size()); + for (size_t i = 0; i < expected.size(); ++i) { + ValidateValue(actual[i], expected[i]); + } +} + +void ValidateValue(const Json& actual, const Json& expected) { + ASSERT_EQ(actual.type(), expected.type()); + switch (expected.type()) { + case Json::Type::JSON_NULL: + case Json::Type::JSON_TRUE: + case Json::Type::JSON_FALSE: + break; + case Json::Type::STRING: + case Json::Type::NUMBER: + EXPECT_EQ(actual.string_value(), expected.string_value()); + break; + case Json::Type::OBJECT: + ValidateObject(actual.object_value(), expected.object_value()); + break; + case Json::Type::ARRAY: + ValidateArray(actual.array_value(), expected.array_value()); + break; + } +} + +void RunSuccessTest(const char* input, const Json& expected, + const char* expected_output) { + gpr_log(GPR_INFO, "parsing string \"%s\" - should succeed", input); + grpc_error* error = GRPC_ERROR_NONE; + Json json = Json::Parse(input, &error); + ASSERT_EQ(error, GRPC_ERROR_NONE) << grpc_error_string(error); + ValidateValue(json, expected); + std::string output = json.Dump(); + EXPECT_EQ(output, expected_output); +} + +TEST(Json, Whitespace) { + RunSuccessTest(" 0 ", 0, "0"); + RunSuccessTest(" 1 ", 1, "1"); + RunSuccessTest(" \" \" ", " ", "\" \""); + RunSuccessTest(" \"a\" ", "a", "\"a\""); + RunSuccessTest(" true ", true, "true"); +} + +TEST(Json, Utf16) { + RunSuccessTest("\"\\u0020\\\\\\u0010\\u000a\\u000D\"", " \\\u0010\n\r", + "\" \\\\\\u0010\\n\\r\""); +} + +TEST(Json, Utf8) { + RunSuccessTest("\"ßâñć௵⇒\"", "ßâñć௵⇒", + "\"\\u00df\\u00e2\\u00f1\\u0107\\u0bf5\\u21d2\""); + RunSuccessTest("\"\\u00df\\u00e2\\u00f1\\u0107\\u0bf5\\u21d2\"", "ßâñć௵⇒", + "\"\\u00df\\u00e2\\u00f1\\u0107\\u0bf5\\u21d2\""); + // Testing UTF-8 character "𝄞", U+11D1E. + RunSuccessTest("\"\xf0\x9d\x84\x9e\"", "\xf0\x9d\x84\x9e", + "\"\\ud834\\udd1e\""); + RunSuccessTest("\"\\ud834\\udd1e\"", "\xf0\x9d\x84\x9e", + "\"\\ud834\\udd1e\""); + RunSuccessTest("{\"\\ud834\\udd1e\":0}", + Json::Object{{"\xf0\x9d\x84\x9e", 0}}, + "{\"\\ud834\\udd1e\":0}"); +} + +TEST(Json, NestedEmptyContainers) { + RunSuccessTest(" [ [ ] , { } , [ ] ] ", + Json::Array{ + Json::Array(), + Json::Object(), + Json::Array(), + }, + "[[],{},[]]"); +} + +TEST(Json, EscapesAndControlCharactersInKeyStrings) { + RunSuccessTest(" { \"\\u007f\x7f\\n\\r\\\"\\f\\b\\\\a , b\": 1, \"\": 0 } ", + Json::Object{ + {"\u007f\u007f\n\r\"\f\b\\a , b", 1}, + {"", 0}, + }, + "{\"\":0,\"\\u007f\\u007f\\n\\r\\\"\\f\\b\\\\a , b\":1}"); +} + +TEST(Json, WriterCutsOffInvalidUtf8) { + RunSuccessTest("\"abc\xf0\x9d\x24\"", "abc\xf0\x9d\x24", "\"abc\""); + RunSuccessTest("\"\xff\"", "\xff", "\"\""); +} + +TEST(Json, ValidNumbers) { + RunSuccessTest("[0, 42 , 0.0123, 123.456]", + Json::Array{ + 0, + 42, + Json("0.0123", /*is_number=*/true), + Json("123.456", /*is_number=*/true), + }, + "[0,42,0.0123,123.456]"); + RunSuccessTest("[1e4,-53.235e-31, 0.3e+3]", + Json::Array{ + Json("1e4", /*is_number=*/true), + Json("-53.235e-31", /*is_number=*/true), + Json("0.3e+3", /*is_number=*/true), + }, + "[1e4,-53.235e-31,0.3e+3]"); +} + +TEST(Json, Keywords) { + RunSuccessTest("[true, false, null]", + Json::Array{ + Json(true), + Json(false), + Json(), + }, + "[true,false,null]"); +} + +void RunParseFailureTest(const char* input) { + gpr_log(GPR_INFO, "parsing string \"%s\" - should fail", input); + grpc_error* error = GRPC_ERROR_NONE; + Json json = Json::Parse(input, &error); + gpr_log(GPR_INFO, "error: %s", grpc_error_string(error)); + EXPECT_NE(error, GRPC_ERROR_NONE); + GRPC_ERROR_UNREF(error); +} + +TEST(Json, InvalidInput) { + RunParseFailureTest("\\"); + RunParseFailureTest("nu ll"); + RunParseFailureTest("{\"foo\": bar}"); + RunParseFailureTest("{\"foo\": bar\"x\"}"); + RunParseFailureTest("fals"); + RunParseFailureTest("0,0 "); + RunParseFailureTest("\"foo\",[]"); +} + +TEST(Json, UnterminatedString) { RunParseFailureTest("\"\\x"); } + +TEST(Json, InvalidUtf16) { + RunParseFailureTest("\"\\u123x"); + RunParseFailureTest("{\"\\u123x"); +} + +TEST(Json, ImbalancedSurrogatePairs) { + RunParseFailureTest("\"\\ud834f"); + RunParseFailureTest("{\"\\ud834f\":0}"); + RunParseFailureTest("\"\\ud834\\n"); + RunParseFailureTest("{\"\\ud834\\n\":0}"); + RunParseFailureTest("\"\\udd1ef"); + RunParseFailureTest("{\"\\udd1ef\":0}"); + RunParseFailureTest("\"\\ud834\\ud834\""); + RunParseFailureTest("{\"\\ud834\\ud834\"\":0}"); + RunParseFailureTest("\"\\ud834\\u1234\""); + RunParseFailureTest("{\"\\ud834\\u1234\"\":0}"); + RunParseFailureTest("\"\\ud834]\""); + RunParseFailureTest("{\"\\ud834]\"\":0}"); + RunParseFailureTest("\"\\ud834 \""); + RunParseFailureTest("{\"\\ud834 \"\":0}"); + RunParseFailureTest("\"\\ud834\\\\\""); + RunParseFailureTest("{\"\\ud834\\\\\"\":0}"); +} + +TEST(Json, EmbeddedInvalidWhitechars) { + RunParseFailureTest("\"\n\""); + RunParseFailureTest("\"\t\""); +} + +TEST(Json, EmptyString) { RunParseFailureTest(""); } + +TEST(Json, ExtraCharsAtEndOfParsing) { + RunParseFailureTest("{},"); + RunParseFailureTest("{}x"); +} + +TEST(Json, ImbalancedContainers) { + RunParseFailureTest("{}}"); + RunParseFailureTest("[]]"); + RunParseFailureTest("{{}"); + RunParseFailureTest("[[]"); + RunParseFailureTest("[}"); + RunParseFailureTest("{]"); +} + +TEST(Json, BadContainers) { + RunParseFailureTest("{x}"); + RunParseFailureTest("{x=0,y}"); +} + +TEST(Json, DuplicateObjectKeys) { RunParseFailureTest("{\"x\": 1, \"x\": 1}"); } + +TEST(Json, TrailingComma) { + RunParseFailureTest("{,}"); + RunParseFailureTest("[1,2,3,4,]"); + RunParseFailureTest("{\"a\": 1, }"); +} + +TEST(Json, KeySyntaxInArray) { RunParseFailureTest("[\"x\":0]"); } + +TEST(Json, InvalidNumbers) { + RunParseFailureTest("1."); + RunParseFailureTest("1e"); + RunParseFailureTest(".12"); + RunParseFailureTest("1.x"); + RunParseFailureTest("1.12x"); + RunParseFailureTest("1ex"); + RunParseFailureTest("1e12x"); + RunParseFailureTest(".12x"); + RunParseFailureTest("000"); +}; + +TEST(Json, Equality) { + // Null. + EXPECT_EQ(Json(), Json()); + // Numbers. + EXPECT_EQ(Json(1), Json(1)); + EXPECT_NE(Json(1), Json(2)); + EXPECT_EQ(Json(1), Json("1", /*is_number=*/true)); + EXPECT_EQ(Json("-5e5", /*is_number=*/true), Json("-5e5", /*is_number=*/true)); + // Booleans. + EXPECT_EQ(Json(true), Json(true)); + EXPECT_EQ(Json(false), Json(false)); + EXPECT_NE(Json(true), Json(false)); + // Strings. + EXPECT_EQ(Json("foo"), Json("foo")); + EXPECT_NE(Json("foo"), Json("bar")); + // Arrays. + EXPECT_EQ(Json(Json::Array{"foo"}), Json(Json::Array{"foo"})); + EXPECT_NE(Json(Json::Array{"foo"}), Json(Json::Array{"bar"})); + // Objects. + EXPECT_EQ(Json(Json::Object{{"foo", 1}}), Json(Json::Object{{"foo", 1}})); + EXPECT_NE(Json(Json::Object{{"foo", 1}}), Json(Json::Object{{"foo", 2}})); + EXPECT_NE(Json(Json::Object{{"foo", 1}}), Json(Json::Object{{"bar", 1}})); + // Differing types. + EXPECT_NE(Json(1), Json("foo")); + EXPECT_NE(Json(1), Json(true)); + EXPECT_NE(Json(1), Json(Json::Array{})); + EXPECT_NE(Json(1), Json(Json::Object{})); + EXPECT_NE(Json(1), Json()); +} + +} // namespace grpc_core + +int main(int argc, char** argv) { + grpc::testing::TestEnvironment env(argc, argv); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tools/doxygen/Doxyfile.core.internal b/tools/doxygen/Doxyfile.core.internal index 903f69ddacc..7314b7dd8cd 100644 --- a/tools/doxygen/Doxyfile.core.internal +++ b/tools/doxygen/Doxyfile.core.internal @@ -1466,7 +1466,9 @@ src/core/lib/iomgr/wakeup_fd_posix.h \ src/core/lib/json/json.cc \ src/core/lib/json/json.h \ src/core/lib/json/json_reader.cc \ +src/core/lib/json/json_reader_new.cc \ src/core/lib/json/json_writer.cc \ +src/core/lib/json/json_writer_new.cc \ src/core/lib/profiling/basic_timers.cc \ src/core/lib/profiling/stap_timers.cc \ src/core/lib/profiling/timers.h \ diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json index a3a658f3e36..221c3e3f6a0 100644 --- a/tools/run_tests/generated/tests.json +++ b/tools/run_tests/generated/tests.json @@ -5019,6 +5019,30 @@ ], "uses_polling": true }, + { + "args": [], + "benchmark": false, + "ci_platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "cpu_cost": 1.0, + "exclude_configs": [], + "exclude_iomgrs": [], + "flaky": false, + "gtest": true, + "language": "c++", + "name": "json_test_new", + "platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "uses_polling": false + }, { "args": [], "benchmark": false,