blob: 7d6d00106b69799f7fbd9b93a075d93037bbefe5 [file] [log] [blame]
// Copyright 2024 The Pigweed 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
//
// https://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 "pw_json/builder.h"
#include <array>
#include <cstring>
#include <string_view>
#include "pw_assert/check.h"
#include "pw_compilation_testing/negative_compilation.h"
#include "pw_unit_test/framework.h"
namespace {
using namespace std::string_view_literals;
// First example for the docs.
static_assert([] {
bool is_simple = true;
int safety_percentage = 100;
std::string_view features[] = {"values", "arrays", "objects", "nesting!"};
// DOCTSAG: [pw-json-builder-example-1]
pw::JsonBuffer<256> json_buffer;
pw::JsonObject& object = json_buffer.StartObject();
object.Add("tagline", "Easy, efficient JSON serialization!")
.Add("simple", is_simple)
.Add("safe", safety_percentage)
.Add("dynamic allocation", false);
pw::NestedJsonArray nested_array = object.AddNestedArray("features");
for (const std::string_view feature : features) {
nested_array.Append(feature);
}
// DOCTSAG: [pw-json-builder-example-1]
return json_buffer;
}() == R"({"tagline": "Easy, efficient JSON serialization!", "simple": true,)"
R"( "safe": 100, "dynamic allocation": false, "features":)"
R"( ["values", "arrays", "objects", "nesting!"]})"sv);
// Second example for the docs.
static_assert([] {
constexpr char empty[128] = {};
std::string_view huge_string_that_wont_fit(empty, sizeof(empty));
// DOCTSAG: [pw-json-builder-example-2]
// Declare a JsonBuffer (JsonBuilder with included buffer) and start a JSON
// object in it.
pw::JsonBuffer<128> json_buffer;
pw::JsonObject& json = json_buffer.StartObject();
const char* name = "Crag";
constexpr const char* kOccupationKey = "job";
// Add key-value pairs to a JSON object.
json.Add("name", name).Add(kOccupationKey, "hacker");
// Add an array as the value in a key-value pair.
pw::NestedJsonArray nested_array = json.AddNestedArray("skills");
// Append items to an array.
nested_array.Append(20).Append(1).Append(1).Append(1);
// Check that everything fit in the JSON buffer.
PW_ASSERT(json.ok());
// Compare the contents of the JSON to a std::string_view.
PW_ASSERT(std::string_view(json) ==
R"({"name": "Crag", "job": "hacker", "skills": [20, 1, 1, 1]})");
// Add an object as the value in a key-value pair.
pw::NestedJsonObject nested_object = json.AddNestedObject("items");
// Declare another JsonArray, and add it as nested value.
pw::JsonBuffer<10> inner_buffer;
inner_buffer.StartArray().Append(nullptr);
nested_object.Add("misc", inner_buffer);
// Add a value that is too large for the JsonBuffer.
json.Add("way too big!", huge_string_that_wont_fit);
// Adding the last entry failed, but the JSON is still valid.
PW_ASSERT(json.status().IsResourceExhausted());
PW_ASSERT(std::string_view(json) ==
R"({"name": "Crag", "job": "hacker", "skills": [20, 1, 1, 1],)"
R"( "items": {"misc": [null]}})");
// DOCTSAG: [pw-json-builder-example-2]
return json_buffer;
}() == R"({"name": "Crag", "job": "hacker", "skills": [20, 1, 1, 1],)"
R"( "items": {"misc": [null]}})"sv);
} // namespace
namespace pw {
namespace {
class JsonOverflowTest : public ::testing::Test {
public:
~JsonOverflowTest() override {
EXPECT_STREQ(&buffer_[end_], kTag) << "Overflow occurred!";
}
protected:
void MarkBufferEnd(const JsonBuilder& json) {
end_ = json.max_size() + 1;
ASSERT_LT(end_, sizeof(buffer_) - sizeof(kTag));
std::memcpy(&buffer_[end_], kTag, sizeof(kTag));
}
char buffer_[512];
private:
static constexpr const char kTag[] = "Hi! Your buffer is safe.";
size_t end_ = 0;
};
TEST(JsonObject, BasicJson) {
char buffer[128];
std::memset(buffer, '?', sizeof(buffer));
JsonBuilder json_buffer(buffer);
JsonObject& json = json_buffer.StartObject();
EXPECT_EQ(buffer, json.data());
EXPECT_STREQ("{}", json.data());
EXPECT_EQ(2u, json.size());
EXPECT_EQ(OkStatus(), json.Add("foo", "bar").status());
EXPECT_STREQ(R"({"foo": "bar"})", json.data());
EXPECT_EQ(std::strlen(json.data()), json.size());
EXPECT_EQ(OkStatus(), json.Add("bar", 0).status());
EXPECT_STREQ(R"({"foo": "bar", "bar": 0})", json.data());
EXPECT_EQ(std::strlen(json.data()), json.size());
EXPECT_EQ(OkStatus(), json.Add("baz", nullptr).status());
EXPECT_STREQ(R"({"foo": "bar", "bar": 0, "baz": null})", json.data());
EXPECT_EQ(std::strlen(json.data()), json.size());
EXPECT_EQ(OkStatus(), json.Add("EMPTY STR!", "").status());
EXPECT_STREQ(R"({"foo": "bar", "bar": 0, "baz": null, "EMPTY STR!": ""})",
json.data());
EXPECT_EQ(std::strlen(json.data()), json.size());
}
TEST(JsonObject, OverflowAtKey) {
JsonBuffer<19> json_buffer;
JsonObject& json = json_buffer.StartObject();
EXPECT_EQ(OkStatus(), json.Add("a", 5l).Add("b", "!").status());
EXPECT_STREQ(R"({"a": 5, "b": "!"})", json.data()); // 18 chars + \0
EXPECT_EQ(Status::ResourceExhausted(), json.Add("b", "!").status());
EXPECT_STREQ(R"({"a": 5, "b": "!"})", json.data());
EXPECT_EQ(Status::ResourceExhausted(), json.Add("b", "").status());
EXPECT_STREQ(R"({"a": 5, "b": "!"})", json.data());
EXPECT_EQ(Status::ResourceExhausted(), json.status());
EXPECT_EQ(Status::ResourceExhausted(), json.last_status());
json.clear_status();
EXPECT_STREQ(R"({"a": 5, "b": "!"})", json.data());
EXPECT_EQ(OkStatus(), json.status());
EXPECT_EQ(OkStatus(), json.last_status());
}
TEST_F(JsonOverflowTest, ObjectOverflowAtFirstEntry) {
JsonBuilder json_builder(buffer_, 5);
MarkBufferEnd(json_builder);
JsonObject& json = json_builder.StartObject();
ASSERT_STREQ("{}", json.data());
EXPECT_EQ(Status::ResourceExhausted(), json.Add("some_key", "").status());
EXPECT_STREQ("{}", json.data());
}
TEST_F(JsonOverflowTest, ObjectOverflowAtStringValue) {
JsonBuilder json_builder(buffer_, 32);
MarkBufferEnd(json_builder);
JsonObject& json = json_builder.StartObject();
EXPECT_EQ(OkStatus(), json.Add("a", 5l).status());
EXPECT_STREQ(R"({"a": 5})", json.data());
EXPECT_EQ(
Status::ResourceExhausted(),
json.Add("b", "This string is so long that it won't fit!!!!").status());
EXPECT_STREQ(R"({"a": 5})", json.data());
EXPECT_EQ(Status::ResourceExhausted(), json.status());
EXPECT_EQ(OkStatus(), json.Add("b", "This will!").last_status());
EXPECT_STREQ(R"({"a": 5, "b": "This will!"})", json.data());
EXPECT_EQ(Status::ResourceExhausted(), json.status());
EXPECT_EQ(OkStatus(), json.last_status());
}
TEST_F(JsonOverflowTest, OverflowAtUnicodeCharacter) {
JsonBuilder json_builder(buffer_, 10);
MarkBufferEnd(json_builder);
JsonValue& overflow_at_unicode = json_builder.StartValue();
EXPECT_EQ(Status::ResourceExhausted(), overflow_at_unicode.Set("234\x01"));
EXPECT_STREQ("null", overflow_at_unicode.data());
EXPECT_EQ(Status::ResourceExhausted(), overflow_at_unicode.Set("2345\x01"));
EXPECT_STREQ("null", overflow_at_unicode.data());
EXPECT_EQ(Status::ResourceExhausted(),
overflow_at_unicode.Set("23456789\x01"));
EXPECT_STREQ("null", overflow_at_unicode.data());
}
TEST_F(JsonOverflowTest, ObjectOverflowAtNumber) {
JsonBuilder json_builder(buffer_, 14);
MarkBufferEnd(json_builder);
JsonObject& json = json_builder.StartObject();
EXPECT_EQ(OkStatus(), json.Add("a", 123456).status());
EXPECT_STREQ(R"({"a": 123456})", json.data());
EXPECT_EQ(json.max_size(), json.size());
json.clear();
EXPECT_EQ(Status::ResourceExhausted(), json.Add("a", 1234567).status());
EXPECT_STREQ(R"({})", json.data());
EXPECT_EQ(2u, json.size());
json.clear();
EXPECT_EQ(Status::ResourceExhausted(), json.Add("a", 12345678).status());
EXPECT_STREQ(R"({})", json.data());
EXPECT_EQ(2u, json.size());
}
TEST(JsonObject, StringValueFillsAllSpace) {
JsonBuffer<15> json_buffer;
JsonObject& json = json_buffer.StartObject();
EXPECT_EQ(OkStatus(), json.Add("key", "12\\").status());
EXPECT_STREQ(R"({"key": "12\\"})", json.data());
EXPECT_EQ(15u, json.size());
json.clear();
EXPECT_EQ(Status::ResourceExhausted(), json.Add("key", "123\\").status());
EXPECT_STREQ(R"({})", json.data());
}
TEST(JsonObject, NestedJson) {
JsonBuffer<64> outside_builder;
JsonBuffer<32> inside_builder;
JsonObject& outside = outside_builder.StartObject();
JsonObject& inside = inside_builder.StartObject();
ASSERT_EQ(OkStatus(), inside.Add("inside", 123).status());
ASSERT_STREQ(R"({"inside": 123})", inside.data());
EXPECT_EQ(OkStatus(), outside.Add("some_value", inside).status());
EXPECT_STREQ(R"({"some_value": {"inside": 123}})", outside.data());
inside.clear();
EXPECT_EQ(OkStatus(), outside.Add("MT", inside).status());
EXPECT_STREQ(R"({"some_value": {"inside": 123}, "MT": {}})", outside.data());
outside.AddNestedArray("key").Append(99).Append(1);
EXPECT_EQ(outside,
R"({"some_value": {"inside": 123}, "MT": {}, "key": [99, 1]})"sv);
}
TEST(JsonObject, NestedArrayOverflowWhenNesting) {
JsonBuffer<5> buffer;
JsonArray& array = buffer.StartArray();
array.Append(123);
ASSERT_EQ(array, "[123]"sv);
NestedJsonArray nested_array = array.AppendNestedArray();
EXPECT_EQ(Status::ResourceExhausted(), array.status());
nested_array.Append(1);
EXPECT_EQ(array, "[123]"sv);
}
TEST(JsonObject, NestedArrayOverflowAppend) {
JsonBuffer<5> buffer;
JsonArray& array = buffer.StartArray();
NestedJsonArray nested_array = array.AppendNestedArray();
EXPECT_EQ(OkStatus(), array.status());
nested_array.Append(10);
EXPECT_EQ(Status::ResourceExhausted(), array.status());
EXPECT_EQ(array, "[[]]"sv);
}
TEST(JsonObject, NestedArrayOverflowSecondAppend) {
JsonBuffer<7> buffer;
JsonArray& array = buffer.StartArray();
NestedJsonArray nested_array = array.AppendNestedArray();
EXPECT_EQ(OkStatus(), array.status());
nested_array.Append(1);
EXPECT_EQ(array, "[[1]]"sv);
EXPECT_EQ(OkStatus(), array.status());
nested_array.Append(2);
EXPECT_EQ(array, "[[1]]"sv);
EXPECT_EQ(Status::ResourceExhausted(), array.status());
}
TEST(JsonObject, NestedObjectOverflowWhenNesting) {
JsonBuffer<5> buffer;
JsonArray& array = buffer.StartArray();
array.Append(123);
ASSERT_EQ(array, "[123]"sv);
std::ignore = array.AppendNestedObject();
EXPECT_EQ(Status::ResourceExhausted(), array.status());
EXPECT_EQ(array, "[123]"sv);
}
TEST(JsonObject, NestedObjectOverflowAppend) {
JsonBuffer<5> buffer;
JsonArray& array = buffer.StartArray();
NestedJsonObject nested_object = array.AppendNestedObject();
EXPECT_EQ(OkStatus(), array.status());
nested_object.Add("k", 10);
EXPECT_EQ(Status::ResourceExhausted(), array.status());
EXPECT_EQ(array, "[{}]"sv);
}
TEST(JsonObject, NestedObjectOverflowSecondAppend) {
JsonBuffer<14> buffer;
JsonArray& array = buffer.StartArray();
NestedJsonObject nested_object = array.AppendNestedObject();
EXPECT_EQ(OkStatus(), array.status());
nested_object.Add("k", 1);
EXPECT_EQ(array, R"([{"k": 1}])"sv);
EXPECT_EQ(OkStatus(), array.status());
nested_object.Add("K", 2);
EXPECT_EQ(array, R"([{"k": 1}])"sv);
EXPECT_EQ(Status::ResourceExhausted(), array.status());
}
TEST_F(JsonOverflowTest, ObjectNestedJsonOverflow) {
JsonBuffer<32> inside_buffer;
JsonObject& inside = inside_buffer.StartObject();
JsonBuilder outside_builder(buffer_, 20);
MarkBufferEnd(outside_builder);
JsonObject& outside = outside_builder.StartObject();
ASSERT_EQ(OkStatus(), inside.Add("k", 78).status());
ASSERT_EQ(9u, inside.size()); // 9 bytes, will fit
EXPECT_EQ(OkStatus(), outside.Add("data", inside).status());
EXPECT_STREQ(R"({"data": {"k": 78}})", outside.data()); // 20 bytes total
inside.clear();
ASSERT_EQ(OkStatus(), inside.Add("k", 789).status());
ASSERT_EQ(10u, inside.size()); // 10 bytes, won't fit
outside.clear();
EXPECT_EQ(Status::ResourceExhausted(), outside.Add("data", inside).status());
EXPECT_EQ(Status::ResourceExhausted(), outside.last_status());
EXPECT_EQ(Status::ResourceExhausted(), outside.status());
EXPECT_STREQ(R"({})", outside.data());
inside.clear();
EXPECT_EQ(OkStatus(), outside.Add("data", inside).last_status());
EXPECT_EQ(OkStatus(), outside.last_status());
EXPECT_EQ(Status::ResourceExhausted(), outside.status());
}
TEST(JsonValue, BasicValues) {
JsonBuffer<13> json;
EXPECT_EQ(OkStatus(), json.SetValue(-15));
EXPECT_STREQ("-15", json.data());
EXPECT_EQ(3u, json.size());
EXPECT_EQ(OkStatus(), json.SetValue(0));
EXPECT_STREQ("0", json.data());
EXPECT_EQ(1u, json.size());
EXPECT_EQ(OkStatus(), json.SetValue(static_cast<char>(35)));
EXPECT_STREQ("35", json.data());
EXPECT_EQ(2u, json.size());
EXPECT_EQ(OkStatus(), json.SetValue(nullptr));
EXPECT_STREQ("null", json.data());
EXPECT_EQ(4u, json.size());
EXPECT_EQ(OkStatus(), json.SetValue(static_cast<const char*>(nullptr)));
EXPECT_STREQ("null", json.data());
EXPECT_EQ(4u, json.size());
EXPECT_EQ(OkStatus(), json.SetValue(""));
EXPECT_STREQ(R"("")", json.data());
EXPECT_EQ(2u, json.size());
EXPECT_EQ(OkStatus(), json.SetValue("Hey\n!"));
EXPECT_STREQ(R"("Hey\n!")", json.data());
EXPECT_EQ(8u, json.size());
JsonValue& json_value = json.StartValue();
EXPECT_STREQ("null", json_value.data());
char str[] = R"(Qu"o"tes)";
EXPECT_EQ(OkStatus(), json_value.Set(str));
EXPECT_STREQ(R"("Qu\"o\"tes")", json_value.data());
EXPECT_EQ(12u, json_value.size());
EXPECT_EQ(OkStatus(), json_value.Set(true));
EXPECT_STREQ("true", json_value.data());
EXPECT_EQ(4u, json_value.size());
bool false_value = false;
EXPECT_EQ(OkStatus(), json.SetValue(false_value));
EXPECT_STREQ("false", json.data());
EXPECT_EQ(5u, json.size());
EXPECT_EQ(OkStatus(), json_value.Set(static_cast<double>(1)));
EXPECT_EQ(json_value, "1"sv);
EXPECT_EQ(OkStatus(), json_value.Set(-1.0f));
EXPECT_EQ(json_value, "-1"sv);
}
TEST_F(JsonOverflowTest, ValueOverflowUnquoted) {
JsonBuilder json(buffer_, 5);
MarkBufferEnd(json);
ASSERT_EQ(4u, json.max_size());
EXPECT_EQ(Status::ResourceExhausted(), json.SetValue(12345));
EXPECT_STREQ("null", json.data());
EXPECT_EQ(4u, json.size());
EXPECT_EQ(OkStatus(), json.SetValue(1234));
EXPECT_STREQ("1234", json.data());
EXPECT_EQ(4u, json.size());
EXPECT_EQ(Status::ResourceExhausted(), json.SetValue(false));
EXPECT_STREQ("null", json.data());
EXPECT_EQ(4u, json.size());
EXPECT_EQ(OkStatus(), json.SetValue(true));
EXPECT_STREQ("true", json.data());
EXPECT_EQ(4u, json.size());
}
TEST_F(JsonOverflowTest, ValueOverflowQuoted) {
JsonBuilder json(buffer_, 8);
MarkBufferEnd(json);
ASSERT_EQ(7u, json.max_size());
EXPECT_EQ(OkStatus(), json.SetValue("34567"));
EXPECT_STREQ(R"("34567")", json.data());
EXPECT_EQ(7u, json.size());
EXPECT_EQ(Status::ResourceExhausted(), json.SetValue("345678"));
EXPECT_STREQ("null", json.data());
EXPECT_EQ(4u, json.size());
EXPECT_EQ(OkStatus(), json.SetValue("567\n"));
EXPECT_STREQ(R"("567\n")", json.data());
EXPECT_EQ(7u, json.size());
EXPECT_EQ(Status::ResourceExhausted(), json.SetValue("5678\n"));
EXPECT_STREQ("null", json.data());
EXPECT_EQ(4u, json.size());
EXPECT_EQ(Status::ResourceExhausted(), json.SetValue("\x05"));
EXPECT_STREQ("null", json.data());
EXPECT_EQ(4u, json.size());
JsonBuffer<9> bigger_json;
EXPECT_EQ(OkStatus(), bigger_json.SetValue("\x05"));
EXPECT_STREQ(R"("\u0005")", bigger_json.data());
EXPECT_EQ(8u, bigger_json.size());
}
TEST(JsonValue, NestedJson) {
JsonBuffer<11> json;
JsonBuffer<12> object_buffer;
JsonObject& object = object_buffer.StartObject();
ASSERT_EQ(OkStatus(), object.Add("3", 7890).status());
ASSERT_STREQ(R"({"3": 7890})", object.data());
ASSERT_EQ(json.max_size(), object.size());
EXPECT_EQ(OkStatus(), json.SetValue(object));
EXPECT_STREQ(R"({"3": 7890})", json.data());
EXPECT_EQ(11u, json.size());
object.clear();
ASSERT_EQ(OkStatus(), object.Add("3", 78901).status());
ASSERT_STREQ(R"({"3": 78901})", object.data());
ASSERT_EQ(object.max_size(), object.size());
ASSERT_GT(object.size(), json.size());
EXPECT_EQ(Status::ResourceExhausted(), json.SetValue(object));
EXPECT_STREQ("null", json.data());
EXPECT_EQ(4u, json.size());
JsonBuffer<12> value;
const char* something = nullptr;
EXPECT_EQ(OkStatus(), value.SetValue(something));
EXPECT_EQ(OkStatus(), json.SetValue(value));
EXPECT_STREQ("null", json.data());
EXPECT_EQ(4u, json.size());
}
TEST(JsonValue, SetFromOtherJsonValue) {
JsonBuffer<32> first = JsonBuffer<32>::Value("$$02$ok$$C");
constexpr const char kExpected[] = R"("$$02$ok$$C")";
ASSERT_STREQ(kExpected, first.data());
ASSERT_EQ(sizeof(kExpected) - 1, first.size());
JsonBuffer<24> second;
EXPECT_EQ(OkStatus(), second.SetValue(first));
EXPECT_STREQ(kExpected, second.data());
EXPECT_EQ(sizeof(kExpected) - 1, second.size());
}
TEST(JsonValue, ToJsonValue) {
static constexpr auto value = JsonBuffer<4>::Value(1234);
EXPECT_STREQ("1234", value.data());
EXPECT_STREQ("\"1234\"", JsonBuffer<6>::Value("1234").data());
EXPECT_STREQ("null", JsonBuffer<4>::Value(nullptr).data());
EXPECT_STREQ("false", JsonBuffer<5>::Value(false).data());
#if PW_NC_TEST(ValueDoesNotFit)
PW_NC_EXPECT("PW_ASSERT\(json.SetValue\(initial_value\).ok\(\)\)");
[[maybe_unused]] static constexpr auto fail = JsonBuffer<4>::Value(12345);
#endif // PW_NC_TEST
}
static_assert([] {
JsonBuffer<32> buffer;
buffer.StartObject().Add("hello", "world").Add("ptr", nullptr);
return buffer;
}() == std::string_view(R"({"hello": "world", "ptr": null})"));
TEST(JsonArray, BasicUse) {
JsonBuffer<48> list_buffer;
JsonArray& list = list_buffer.StartArray();
ASSERT_EQ(OkStatus(), list.Append(nullptr).last_status());
ASSERT_EQ(OkStatus(), list.Append("what").status());
JsonBuffer<96> big_list_buffer;
JsonArray& big_list = big_list_buffer.StartArray();
EXPECT_EQ(OkStatus(), big_list.Append(list).status());
EXPECT_EQ(OkStatus(), big_list.Append(123).status());
JsonBuffer<48> object_buffer;
JsonObject& object = object_buffer.StartObject();
ASSERT_EQ(OkStatus(), object.Add("foo", "bar").status());
ASSERT_EQ(OkStatus(), object.Add("bar", list).status());
EXPECT_EQ(OkStatus(), big_list.Append(object).status());
EXPECT_EQ(OkStatus(), big_list.Append('\0').status());
std::array<bool, 2> bools{true, false};
EXPECT_EQ(OkStatus(), big_list.Extend(bools).status());
const char kExpected[] =
R"([[null, "what"], 123, {"foo": "bar", "bar": [null, "what"]}, )"
R"(0, true, false])";
EXPECT_STREQ(kExpected, big_list.data());
EXPECT_EQ(sizeof(kExpected) - 1, big_list.size());
}
TEST(JsonArray, FromArray) {
JsonBuffer<31> array_buffer;
JsonArray& array = array_buffer.StartArray();
EXPECT_EQ(OkStatus(), array.Extend({1, 2, 3, 4, 5}).status());
EXPECT_STREQ("[1, 2, 3, 4, 5]", array.data());
EXPECT_EQ(15u, array.size());
EXPECT_EQ(OkStatus(), array.Extend({6, 7, 8, 9, 0}).status());
EXPECT_STREQ("[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]", array.data());
EXPECT_EQ(30u, array.size());
}
TEST_F(JsonOverflowTest, FromArrayOverflow) {
JsonBuilder array_buffer(buffer_, 31);
MarkBufferEnd(array_buffer);
JsonArray& array = array_buffer.StartArray();
EXPECT_EQ(OkStatus(), array.Extend({1, 2, 3, 4, 5}).status());
EXPECT_STREQ("[1, 2, 3, 4, 5]", array.data());
EXPECT_EQ(15u, array.size());
EXPECT_EQ(Status::ResourceExhausted(),
array.Extend({6, 7, 8, 9, 0, 1, 2, 3}).status());
EXPECT_STREQ("[1, 2, 3, 4, 5]", array.data());
EXPECT_EQ(15u, array.size());
EXPECT_EQ(Status::ResourceExhausted(),
array.Extend({6, 7, 8}).Extend({9, 0}).status());
EXPECT_STREQ("[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]", array.data());
EXPECT_EQ(30u, array.size());
EXPECT_EQ(Status::ResourceExhausted(), array.Extend({5}).status());
EXPECT_STREQ("[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]", array.data());
EXPECT_EQ(30u, array.size());
}
TEST(JsonArray, AppendIndividualExtendContainer) {
JsonBuffer<64> array_buffer;
JsonArray& array = array_buffer.StartArray();
constexpr int kInts[] = {1, 2, 3};
#if PW_NC_TEST(CannotAppendArrays)
PW_NC_EXPECT("JSON values may only be numbers, strings, JSON");
array.Append(kInts);
#endif // PW_NC_TEST
ASSERT_EQ(OkStatus(), array.Extend(kInts).status());
EXPECT_STREQ("[1, 2, 3]", array.data());
}
TEST(JsonArray, NestingArray) {
JsonBuffer<64> array_buffer;
JsonArray& array = array_buffer.StartArray();
std::ignore = array.AppendNestedArray();
EXPECT_STREQ(array.data(), "[[]]");
NestedJsonArray nested = array.AppendNestedArray();
EXPECT_EQ(OkStatus(), array.last_status());
EXPECT_STREQ(array.data(), "[[], []]");
nested.Append(123);
EXPECT_EQ(array.size(), sizeof("[[], [123]]") - 1);
EXPECT_STREQ(array.data(), "[[], [123]]");
nested.Append("");
EXPECT_STREQ(array.data(), "[[], [123, \"\"]]");
}
TEST(JsonArray, NestingObject) {
JsonBuffer<64> array_buffer;
JsonArray& array = array_buffer.StartArray();
NestedJsonObject object = array.AppendNestedObject();
EXPECT_STREQ(array.data(), "[{}]");
object.Add("key", 123);
EXPECT_EQ(array, R"([{"key": 123}])"sv);
object.Add("k", true);
EXPECT_EQ(array, R"([{"key": 123, "k": true}])"sv);
array.AppendNestedArray().Append("done").Append("!");
EXPECT_EQ(array, R"([{"key": 123, "k": true}, ["done", "!"]])"sv);
}
TEST(JsonBuilder, DeepNesting) {
JsonBuffer<64> buffer;
JsonArray& arr1 = buffer.StartArray();
std::ignore = arr1.AppendNestedObject();
EXPECT_EQ(buffer, "[{}]"sv);
auto arr2 = arr1.AppendNestedObject().Add("a", 1).AddNestedArray("b");
arr2.Append(0).Append(1).AppendNestedObject().Add("yes", "no");
arr2.Append(2);
EXPECT_EQ(buffer, R"([{}, {"a": 1, "b": [0, 1, {"yes": "no"}, 2]}])"sv);
arr1.Append(true);
EXPECT_EQ(buffer, R"([{}, {"a": 1, "b": [0, 1, {"yes": "no"}, 2]}, true])"sv);
}
TEST(JsonBuilder, ConvertBetween) {
JsonBuffer<64> buffer;
EXPECT_STREQ("null", buffer.data());
EXPECT_TRUE(buffer.IsValue());
EXPECT_FALSE(buffer.IsObject());
EXPECT_FALSE(buffer.IsArray());
JsonObject& object = buffer.StartObject();
EXPECT_STREQ("{}", buffer.data());
EXPECT_FALSE(object.IsValue());
EXPECT_FALSE(object.IsArray());
EXPECT_TRUE(object.IsObject());
object.Add("123", true);
EXPECT_STREQ(R"({"123": true})", buffer.data());
JsonArray& array = buffer.StartArray();
EXPECT_FALSE(object.IsObject()) << "No longer an object";
EXPECT_TRUE(object.ok()) << "Still OK, just not an object";
EXPECT_FALSE(array.IsValue());
EXPECT_TRUE(array.IsArray());
EXPECT_FALSE(array.IsObject());
EXPECT_STREQ("[]", buffer.data());
EXPECT_EQ(OkStatus(), array.Extend({1, 2, 3}).status());
EXPECT_STREQ("[1, 2, 3]", buffer.data());
EXPECT_EQ(OkStatus(), array.Append(false).Append(-1).status());
EXPECT_STREQ("[1, 2, 3, false, -1]", buffer.data());
object.clear();
EXPECT_EQ(OkStatus(), object.Add("yes", nullptr).status());
EXPECT_STREQ(R"({"yes": null})", buffer.data());
EXPECT_EQ(OkStatus(), buffer.status());
}
static_assert([] {
JsonBuffer<64> buffer;
auto& object = buffer.StartObject();
auto nested_array = object.AddNestedArray("array");
nested_array.Append(1);
object.Add("key", "value");
if (buffer != R"({"array": [1], "key": "value"})"sv) {
return false;
}
#if PW_NC_TEST(NestedJsonAttemptsToDetectWrongType)
PW_NC_EXPECT("PW_ASSERT\(.*// Nested structure must match the expected type");
nested_array.Append(2);
#elif PW_NC_TEST(NestedJsonDetectsClearedJson)
PW_NC_EXPECT("PW_ASSERT\(.*// JSON must not have been cleared since nesting");
object.clear();
nested_array.Append(2);
#endif // PW_NC_TEST
return true;
}());
static_assert([] {
JsonBuffer<64> buffer;
auto& array = buffer.StartArray();
NestedJsonArray nested = array.AppendNestedArray();
for (int i = 1; i < 16; ++i) {
nested = nested.AppendNestedArray();
}
// 17 arrays total (1 outer array, 16 levels of nesting inside it).
// 1234567890123456712345678901234567
if (array != R"([[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]])"sv) {
return JsonBuffer<64>{};
}
nested.Append("-_-");
#if PW_NC_TEST(NestingLimit)
PW_NC_EXPECT(
"PW_ASSERT\(.*// Arrays or objects may be nested at most 17 times");
std::ignore = nested.AppendNestedArray();
#endif // PW_NC_TEST
return buffer;
}() == R"([[[[[[[[[[[[[[[[["-_-"]]]]]]]]]]]]]]]]])"sv);
TEST(JsonBuffer, SetClear) {
JsonBuffer<4> buffer;
EXPECT_EQ(buffer, "null"sv);
ASSERT_EQ(OkStatus(), buffer.SetValue(""));
EXPECT_TRUE(buffer.ok());
EXPECT_EQ(buffer, "\"\""sv);
ASSERT_EQ(Status::ResourceExhausted(), buffer.SetValue("234"));
EXPECT_FALSE(buffer.ok());
EXPECT_EQ(buffer, "null"sv);
buffer.clear();
EXPECT_TRUE(buffer.ok());
EXPECT_EQ(buffer, "null"sv);
}
TEST(JsonBuffer, Copy) {
JsonBuffer<64> foo;
ASSERT_EQ(OkStatus(), foo.SetValue("yes"));
JsonBuffer<48> bar;
auto& object = bar.StartObject().Add("no", true);
EXPECT_EQ(object, R"({"no": true})"sv);
EXPECT_EQ(OkStatus(), bar.StartArray().Append(1).Append(2).status());
foo = bar;
EXPECT_STREQ("[1, 2]", foo.data());
EXPECT_EQ(6u, foo.size());
JsonBuffer<128> baz(foo);
EXPECT_STREQ(foo.data(), baz.data());
}
// Tests character escaping using a table generated with the following Python:
//
// import json
// print(', '.join('R"_({})_"'.format(json.dumps(chr(i))) for i in range(128)))
TEST(JsonBuilder, TestEscape) {
static constexpr std::array<const char*, 128> kEscapedCharacters = {
R"_("\u0000")_", R"_("\u0001")_", R"_("\u0002")_", R"_("\u0003")_",
R"_("\u0004")_", R"_("\u0005")_", R"_("\u0006")_", R"_("\u0007")_",
R"_("\b")_", R"_("\t")_", R"_("\n")_", R"_("\u000b")_",
R"_("\f")_", R"_("\r")_", R"_("\u000e")_", R"_("\u000f")_",
R"_("\u0010")_", R"_("\u0011")_", R"_("\u0012")_", R"_("\u0013")_",
R"_("\u0014")_", R"_("\u0015")_", R"_("\u0016")_", R"_("\u0017")_",
R"_("\u0018")_", R"_("\u0019")_", R"_("\u001a")_", R"_("\u001b")_",
R"_("\u001c")_", R"_("\u001d")_", R"_("\u001e")_", R"_("\u001f")_",
R"_(" ")_", R"_("!")_", R"_("\"")_", R"_("#")_",
R"_("$")_", R"_("%")_", R"_("&")_", R"_("'")_",
R"_("(")_", R"_(")")_", R"_("*")_", R"_("+")_",
R"_(",")_", R"_("-")_", R"_(".")_", R"_("/")_",
R"_("0")_", R"_("1")_", R"_("2")_", R"_("3")_",
R"_("4")_", R"_("5")_", R"_("6")_", R"_("7")_",
R"_("8")_", R"_("9")_", R"_(":")_", R"_(";")_",
R"_("<")_", R"_("=")_", R"_(">")_", R"_("?")_",
R"_("@")_", R"_("A")_", R"_("B")_", R"_("C")_",
R"_("D")_", R"_("E")_", R"_("F")_", R"_("G")_",
R"_("H")_", R"_("I")_", R"_("J")_", R"_("K")_",
R"_("L")_", R"_("M")_", R"_("N")_", R"_("O")_",
R"_("P")_", R"_("Q")_", R"_("R")_", R"_("S")_",
R"_("T")_", R"_("U")_", R"_("V")_", R"_("W")_",
R"_("X")_", R"_("Y")_", R"_("Z")_", R"_("[")_",
R"_("\\")_", R"_("]")_", R"_("^")_", R"_("_")_",
R"_("`")_", R"_("a")_", R"_("b")_", R"_("c")_",
R"_("d")_", R"_("e")_", R"_("f")_", R"_("g")_",
R"_("h")_", R"_("i")_", R"_("j")_", R"_("k")_",
R"_("l")_", R"_("m")_", R"_("n")_", R"_("o")_",
R"_("p")_", R"_("q")_", R"_("r")_", R"_("s")_",
R"_("t")_", R"_("u")_", R"_("v")_", R"_("w")_",
R"_("x")_", R"_("y")_", R"_("z")_", R"_("{")_",
R"_("|")_", R"_("}")_", R"_("~")_", R"_("\u007f")_"};
JsonBuffer<9> buffer;
for (size_t i = 0; i < kEscapedCharacters.size(); ++i) {
const char character = static_cast<char>(i);
ASSERT_EQ(OkStatus(), buffer.SetValue(std::string_view(&character, 1)));
ASSERT_STREQ(kEscapedCharacters[i], buffer.data());
}
}
class JsonObjectTest : public ::testing::Test {
protected:
static constexpr size_t kMaxSize = 127;
static constexpr size_t kBufferSize = kMaxSize + 1;
JsonObjectTest() : object_(json_buffer_.StartObject()) {}
JsonBuffer<kMaxSize> json_buffer_;
JsonObject& object_;
};
TEST_F(JsonObjectTest, TestSingleStringValue) {
EXPECT_EQ(OkStatus(), object_.Add("key", "value").status());
EXPECT_STREQ("{\"key\": \"value\"}", object_.data());
}
TEST_F(JsonObjectTest, TestEscapedQuoteString) {
const char* buf = "{\"key\": \"\\\"value\\\"\"}";
EXPECT_STREQ(buf, object_.Add("key", "\"value\"").data());
}
TEST_F(JsonObjectTest, TestEscapedSlashString) {
const char* buf = "{\"key\": \"\\\\\"}";
EXPECT_STREQ(buf, object_.Add("key", "\\").data());
}
TEST_F(JsonObjectTest, TestEscapedCharactersString) {
const char* buf = "{\"key\": \"\\r\\n\\t\"}";
EXPECT_STREQ(buf, object_.Add("key", "\r\n\t").data());
}
TEST_F(JsonObjectTest, TestEscapedControlCharacterString) {
EXPECT_STREQ("{\"key\": \"\\u001f\"}", object_.Add("key", "\x1F").data());
object_.clear();
EXPECT_STREQ("{\"key\": \"\\u0080\"}", object_.Add("key", "\x80").data());
}
TEST_F(JsonObjectTest, TestNullptrString) {
EXPECT_STREQ("{\"key\": null}",
object_.Add("key", static_cast<const char*>(nullptr)).data());
}
TEST_F(JsonObjectTest, TestCharValue) {
EXPECT_STREQ("{\"key\": 88}",
object_.Add("key", static_cast<unsigned char>('X')).data());
object_.clear();
EXPECT_STREQ("{\"key\": 88}", object_.Add("key", 'X').data());
}
TEST_F(JsonObjectTest, TestShortValue) {
EXPECT_STREQ("{\"key\": 88}",
object_.Add("key", static_cast<unsigned short>(88)).data());
object_.clear();
EXPECT_STREQ("{\"key\": -88}",
object_.Add("key", static_cast<short>(-88)).data());
}
TEST_F(JsonObjectTest, TestIntValue) {
EXPECT_STREQ("{\"key\": 88}",
object_.Add("key", static_cast<unsigned int>(88)).data());
object_.clear();
EXPECT_STREQ("{\"key\": -88}", object_.Add("key", -88).data());
}
TEST_F(JsonObjectTest, TestLongValue) {
EXPECT_STREQ("{\"key\": 88}", object_.Add("key", 88UL).data());
object_.clear();
EXPECT_STREQ("{\"key\": -88}", object_.Add("key", -88L).data());
}
TEST_F(JsonObjectTest, TestMultipleValues) {
char buf[16] = "nonconst";
EXPECT_STREQ("{\"one\": \"nonconst\", \"two\": null, \"three\": -3}",
object_.Add("one", buf)
.Add("two", static_cast<char*>(nullptr))
.Add("three", -3)
.data());
}
TEST_F(JsonObjectTest, TestOverflow) {
// Create a buffer that is just large enough to overflow with "key".
std::array<char, kBufferSize + 1 /* NUL */ - (sizeof("{\"key\": \"\"}") - 1)>
buf;
std::memset(buf.data(), 'z', sizeof(buf));
buf[sizeof(buf) - 1] = '\0';
// Make sure the overflow happens at exactly the right character.
EXPECT_EQ(Status::ResourceExhausted(),
object_.Add("key", buf.data()).status());
EXPECT_EQ(Status::ResourceExhausted(), object_.status());
object_.clear();
EXPECT_EQ(OkStatus(), object_.Add("ke", buf.data()).status());
EXPECT_EQ(OkStatus(), object_.status());
// Ensure the internal buffer is NUL-terminated still, even on overflow.
EXPECT_EQ(object_.data()[object_.size()], '\0');
}
} // namespace
} // namespace pw