Skip to content

Commit

Permalink
json: add null support to the streamer (envoyproxy#36051)
Browse files Browse the repository at this point in the history
Commit Message: json: add null support to the streamer
Additional Description:

I initially think this is unnecessary. But when I retry to update the
json formatter, I found this is still necessary.

Risk Level: low.
Testing: unit.
Docs Changes: n/a.
Release Notes: n/a.
Platform Specific Features: n/a.

---------

Signed-off-by: wangbaiping <[email protected]>
Signed-off-by: wangbaiping <[email protected]>
  • Loading branch information
wbpcode authored Sep 10, 2024
1 parent 2acf901 commit 3f0517b
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 14 deletions.
44 changes: 32 additions & 12 deletions source/common/json/json_streamer.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class StringOutput {
*/
template <class OutputBufferType> class StreamerBase {
public:
using Value = absl::variant<absl::string_view, double, uint64_t, int64_t, bool>;
using Value = absl::variant<absl::string_view, double, uint64_t, int64_t, bool, absl::monostate>;

/**
* @param response The buffer in which to stream output.
Expand Down Expand Up @@ -190,6 +190,18 @@ template <class OutputBufferType> 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
Expand All @@ -211,34 +223,39 @@ template <class OutputBufferType> class StreamerBase {
* @param Value the value to render.
*/
void addValue(const Value& value) {
static_assert(absl::variant_size_v<Value> == 5, "Value must be a variant with 5 types");
static_assert(absl::variant_size_v<Value> == 6, "Value must be a variant with 6 types");

switch (value.index()) {
case 0:
static_assert(std::is_same<decltype(absl::get<0>(value)), const absl::string_view&>::value,
static_assert(std::is_same_v<absl::variant_alternative_t<0, Value>, absl::string_view>,
"value at index 0 must be an absl::string_vlew");
addString(absl::get<absl::string_view>(value));
break;
case 1:
static_assert(std::is_same<decltype(absl::get<1>(value)), const double&>::value,
static_assert(std::is_same_v<absl::variant_alternative_t<1, Value>, double>,
"value at index 1 must be a double");
addNumber(absl::get<double>(value));
break;
case 2:
static_assert(std::is_same<decltype(absl::get<2>(value)), const uint64_t&>::value,
static_assert(std::is_same_v<absl::variant_alternative_t<2, Value>, uint64_t>,
"value at index 2 must be a uint64_t");
addNumber(absl::get<uint64_t>(value));
break;
case 3:
static_assert(std::is_same<decltype(absl::get<3>(value)), const int64_t&>::value,
static_assert(std::is_same_v<absl::variant_alternative_t<3, Value>, int64_t>,
"value at index 3 must be an int64_t");
addNumber(absl::get<int64_t>(value));
break;
case 4:
static_assert(std::is_same<decltype(absl::get<4>(value)), const bool&>::value,
static_assert(std::is_same_v<absl::variant_alternative_t<4, Value>, bool>,
"value at index 4 must be a bool");
addBool(absl::get<bool>(value));
break;
case 5:
static_assert(std::is_same_v<absl::variant_alternative_t<5, Value>, absl::monostate>,
"value at index 5 must be an absl::monostate");
addNull();
break;
}
}

Expand Down Expand Up @@ -385,14 +402,17 @@ template <class OutputBufferType> 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.
Expand Down
45 changes: 43 additions & 2 deletions test/common/json/json_streamer_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ 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_;
};

class StringOutputWrapper {
public:
using Type = StringOutput;
std::string toString() { return underlying_buffer_; }
void clear() { underlying_buffer_.clear(); }
std::string underlying_buffer_;
};

Expand Down Expand Up @@ -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) {
Expand All @@ -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

0 comments on commit 3f0517b

Please sign in to comment.