diff --git a/source/common/json/json_streamer.h b/source/common/json/json_streamer.h index 8087cbf4ada6..ae18b7a38699 100644 --- a/source/common/json/json_streamer.h +++ b/source/common/json/json_streamer.h @@ -81,7 +81,7 @@ class StringOutput { */ template class StreamerBase { public: - using Value = absl::variant; + using Value = absl::variant; /** * @param response The buffer in which to stream output. @@ -190,6 +190,18 @@ template class StreamerBase { streamer_.addBool(b); } + /** + * Adds a null constant value to the current array or map. It's a programming + * error to call this method on a map or array that's not the top level. + * It's also a programming error to call this on map that isn't expecting + * a value. You must call Map::addKey prior to calling this. + */ + void addNull() { + ASSERT_THIS_IS_TOP_LEVEL; + nextField(); + streamer_.addNull(); + } + protected: /** * Initiates a new field, serializing a comma separator if this is not the @@ -211,34 +223,39 @@ template class StreamerBase { * @param Value the value to render. */ void addValue(const Value& value) { - static_assert(absl::variant_size_v == 5, "Value must be a variant with 5 types"); + static_assert(absl::variant_size_v == 6, "Value must be a variant with 6 types"); switch (value.index()) { case 0: - static_assert(std::is_same(value)), const absl::string_view&>::value, + static_assert(std::is_same_v, absl::string_view>, "value at index 0 must be an absl::string_vlew"); addString(absl::get(value)); break; case 1: - static_assert(std::is_same(value)), const double&>::value, + static_assert(std::is_same_v, double>, "value at index 1 must be a double"); addNumber(absl::get(value)); break; case 2: - static_assert(std::is_same(value)), const uint64_t&>::value, + static_assert(std::is_same_v, uint64_t>, "value at index 2 must be a uint64_t"); addNumber(absl::get(value)); break; case 3: - static_assert(std::is_same(value)), const int64_t&>::value, + static_assert(std::is_same_v, int64_t>, "value at index 3 must be an int64_t"); addNumber(absl::get(value)); break; case 4: - static_assert(std::is_same(value)), const bool&>::value, + static_assert(std::is_same_v, bool>, "value at index 4 must be a bool"); addBool(absl::get(value)); break; + case 5: + static_assert(std::is_same_v, absl::monostate>, + "value at index 5 must be an absl::monostate"); + addNull(); + break; } } @@ -385,14 +402,17 @@ template class StreamerBase { void addBool(bool b) { response_.add(b ? Constants::True : Constants::False); } /** - * Adds a pre-sanitized string or which doesn't require sanitizing to the output stream. - * NOTE: use this with care as it bypasses the sanitization process and may result in - * invalid JSON. If you are not sure if the string is already sanitized, use addString() - * or addSanitized() instead. + * Serializes a null to the output stream. */ - void addWithoutSanitizing(absl::string_view str) { response_.add(str); } + void addNull() { response_.add(Constants::Null); } private: + /** + * Adds a string to the output stream without sanitizing it. This is only used to push + * the delimiters to output buffer. + */ + void addWithoutSanitizing(absl::string_view str) { response_.add(str); } + #ifndef NDEBUG /** * @return the top Level*. This is used for asserts. diff --git a/test/common/json/json_streamer_test.cc b/test/common/json/json_streamer_test.cc index 0e332c0ccd8f..8bf9913101c2 100644 --- a/test/common/json/json_streamer_test.cc +++ b/test/common/json/json_streamer_test.cc @@ -15,6 +15,7 @@ class BufferOutputWrapper { public: using Type = BufferOutput; std::string toString() { return underlying_buffer_.toString(); } + void clear() { underlying_buffer_.drain(underlying_buffer_.length()); } Buffer::OwnedImpl underlying_buffer_; }; @@ -22,6 +23,7 @@ class StringOutputWrapper { public: using Type = StringOutput; std::string toString() { return underlying_buffer_; } + void clear() { underlying_buffer_.clear(); } std::string underlying_buffer_; }; @@ -134,9 +136,9 @@ TYPED_TEST(JsonStreamerTest, SubArray) { TYPED_TEST(JsonStreamerTest, TopArray) { { auto array = this->streamer_.makeRootArray(); - array->addEntries({1.0, "two", 3.5, true, false, std::nan("")}); + array->addEntries({1.0, "two", 3.5, true, false, std::nan(""), absl::monostate{}}); } - EXPECT_EQ(R"EOF([1,"two",3.5,true,false,null])EOF", this->buffer_.toString()); + EXPECT_EQ(R"EOF([1,"two",3.5,true,false,null,null])EOF", this->buffer_.toString()); } TYPED_TEST(JsonStreamerTest, SubMap) { @@ -149,6 +151,45 @@ TYPED_TEST(JsonStreamerTest, SubMap) { EXPECT_EQ(R"EOF({"a":{"one":1,"three.5":3.5}})EOF", this->buffer_.toString()); } +TYPED_TEST(JsonStreamerTest, SimpleDirectCall) { + { + this->streamer_.addBool(true); + EXPECT_EQ("true", this->buffer_.toString()); + this->buffer_.clear(); + } + + { + this->streamer_.addBool(false); + EXPECT_EQ("false", this->buffer_.toString()); + this->buffer_.clear(); + } + + { + this->streamer_.addString("hello"); + EXPECT_EQ(R"EOF("hello")EOF", this->buffer_.toString()); + this->buffer_.clear(); + } + + { + uint64_t value = 1; + this->streamer_.addNumber(value); + EXPECT_EQ("1", this->buffer_.toString()); + this->buffer_.clear(); + } + + { + this->streamer_.addNumber(1.5); + EXPECT_EQ("1.5", this->buffer_.toString()); + this->buffer_.clear(); + } + + { + this->streamer_.addNull(); + EXPECT_EQ("null", this->buffer_.toString()); + this->buffer_.clear(); + } +} + } // namespace } // namespace Json } // namespace Envoy