blob: 78aafd5b42884376c59506fbb2fbf3bb8c34e0ba [file]
// Copyright 2026 Google LLC
//
// 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 "cbor/parse.h"
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "absl/status/statusor.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "cbor/cbor.h"
#include "cppbor/cppbor.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "testing/cbor_utils.h"
namespace {
using ::absl_testing::IsOkAndHolds;
using ::absl_testing::StatusIs;
using ::cbor::FromJson;
using ::testing::Eq;
using ::testing::Gt;
using ::testing::HasSubstr;
using ::testing::IsFalse;
using ::testing::IsTrue;
TEST(ParseTest, Okay) {
auto cbor = FromJson(R"json({
"str": "Google",
"uint": 0,
"int": -1,
"nest_map": {"a": 1},
"empty_map": {},
"uint_array": [1, 2, 3],
"int_array": [-1, -2, -3],
"str_array": ["a", "b", "c"],
"array_of_map": [{"a": 1}, {"a": 2}],
"array_of_array": [[1, 2], [3, 4]],
"empty_array": [],
"null_array": [null]
})json");
auto value1 = cbor::Parse(cbor);
ASSERT_TRUE(value1.ok());
auto result = std::move(value1).value();
auto value2 = result->AsMap();
ASSERT_TRUE(value2.ok());
auto map = std::move(value2).value();
EXPECT_THAT(map.GetString("str"), IsOkAndHolds("Google"));
EXPECT_THAT(map.GetUint64("uint"), IsOkAndHolds(0));
EXPECT_THAT(map.GetInt64("int"), IsOkAndHolds(-1));
auto value3 = map.GetMap("nest_map");
ASSERT_TRUE(value3.ok());
auto nest_map = std::move(value3).value();
EXPECT_THAT(nest_map.GetInt64("a"), IsOkAndHolds(1));
auto value4 = map.GetMap("empty_map");
ASSERT_TRUE(value4.ok());
auto empty_map = std::move(value4).value();
EXPECT_THAT(empty_map.GetInt64("a"),
StatusIs(absl::StatusCode::kInvalidArgument));
auto value5 = map.GetArray("uint_array");
ASSERT_TRUE(value5.ok());
auto uint_array = std::move(value5).value();
EXPECT_THAT(uint_array.size(), Eq(3));
for (int i = 0; i < 3; ++i) {
EXPECT_THAT(uint_array.GetUint64(i), IsOkAndHolds(i + 1));
}
auto value6 = map.GetArray("int_array");
ASSERT_TRUE(value6.ok());
auto int_array = std::move(value6).value();
EXPECT_THAT(int_array.size(), Eq(3));
for (int i = 0; i < 3; ++i) {
EXPECT_THAT(int_array.GetInt64(i), IsOkAndHolds(-1 - i));
}
auto value7 = map.GetArray("str_array");
ASSERT_TRUE(value7.ok());
auto str_array = std::move(value7).value();
EXPECT_THAT(str_array.size(), Eq(3));
EXPECT_THAT(str_array.empty(), IsFalse());
for (int i = 0; i < 3; ++i) {
EXPECT_THAT(str_array.GetString(i), IsOkAndHolds(std::string(1, 'a' + i)));
}
auto value8 = map.GetArray("array_of_map");
ASSERT_TRUE(value8.ok());
auto array_of_map = std::move(value8).value();
EXPECT_THAT(array_of_map.size(), Eq(2));
auto value9 = array_of_map.GetMap(0);
ASSERT_TRUE(value9.ok());
auto array_of_map_0 = std::move(value9).value();
EXPECT_THAT(array_of_map_0.GetInt64("a"), IsOkAndHolds(1));
auto value10 = array_of_map.GetMap(1);
ASSERT_TRUE(value10.ok());
auto array_of_map_1 = std::move(value10).value();
EXPECT_THAT(array_of_map_1.GetInt64("a"), IsOkAndHolds(2));
auto value11 = map.GetArray("array_of_array");
ASSERT_TRUE(value11.ok());
auto array_of_array = std::move(value11).value();
EXPECT_THAT(array_of_array.size(), Eq(2));
auto value12 = array_of_array.GetArray(0);
ASSERT_TRUE(value12.ok());
auto array_of_array_0 = std::move(value12).value();
EXPECT_THAT(array_of_array_0.size(), Eq(2));
EXPECT_THAT(array_of_array_0.GetUint64(0), IsOkAndHolds(1));
EXPECT_THAT(array_of_array_0.GetUint64(1), IsOkAndHolds(2));
auto value13 = array_of_array.GetArray(1);
ASSERT_TRUE(value13.ok());
auto array_of_array_1 = std::move(value13).value();
EXPECT_THAT(array_of_array_1.size(), Eq(2));
EXPECT_THAT(array_of_array_1.GetUint64(0), IsOkAndHolds(3));
EXPECT_THAT(array_of_array_1.GetUint64(1), IsOkAndHolds(4));
auto value14 = map.GetArray("empty_array");
ASSERT_TRUE(value14.ok());
auto empty_array = std::move(value14).value();
EXPECT_THAT(empty_array.size(), Eq(0));
EXPECT_THAT(empty_array.empty(), IsTrue());
auto value15 = map.GetArray("null_array");
ASSERT_TRUE(value15.ok());
auto null_array = std::move(value15).value();
EXPECT_THAT(null_array.size(), Eq(1));
EXPECT_THAT(null_array.IsNull(0), IsOkAndHolds(IsTrue()));
EXPECT_THAT(str_array.IsNull(0), IsOkAndHolds(IsFalse()));
}
TEST(ParseTest, SemanticTag) {
cppbor::Map orig_map;
orig_map.add("when",
cppbor::SemanticTag(0, cppbor::Tstr("2026-05-19T00:00:00Z")));
orig_map.add("epoch", cppbor::SemanticTag(1, cppbor::Uint(1684454400)));
auto bytes = orig_map.encode();
absl::string_view bytes_view(reinterpret_cast<const char*>(bytes.data()),
bytes.size());
auto value = cbor::Parse(bytes_view);
ASSERT_TRUE(value.ok());
auto result = std::move(value).value();
auto map_or = result->AsMap();
ASSERT_TRUE(map_or.ok());
auto map = std::move(map_or).value();
EXPECT_THAT(map.GetString("when"), IsOkAndHolds("2026-05-19T00:00:00Z"));
EXPECT_THAT(map.GetUint64("epoch"), IsOkAndHolds(1684454400));
}
TEST(ParseTest, OptionalString) {
auto cbor = FromJson(R"json({"str": "Google", "int": 0})json");
auto value16 = cbor::Parse(cbor);
ASSERT_TRUE(value16.ok());
auto result = std::move(value16).value();
auto value17 = result->AsMap();
ASSERT_TRUE(value17.ok());
auto map = std::move(value17).value();
EXPECT_THAT(map.GetOptionalString("str"), Eq("Google"));
EXPECT_THAT(map.GetOptionalString("not_found"), Eq(std::nullopt));
EXPECT_THAT(map.GetOptionalString("int"), Eq(std::nullopt));
}
TEST(ParseTest, OptionalMap) {
auto cbor = FromJson(
R"json({"not_map": "Google", "map": {"a": 1}, "empty_map": {}})json");
auto value18 = cbor::Parse(cbor);
ASSERT_TRUE(value18.ok());
auto result = std::move(value18).value();
auto value19 = result->AsMap();
ASSERT_TRUE(value19.ok());
auto map = std::move(value19).value();
EXPECT_THAT(map.GetOptionalMap("not_map"), Eq(std::nullopt));
EXPECT_THAT(map.GetOptionalMap("not_found"), Eq(std::nullopt));
EXPECT_TRUE(map.GetOptionalMap("map").has_value());
EXPECT_TRUE(map.GetOptionalMap("empty_map").has_value());
}
TEST(ParseTest, OptionalArray) {
auto cbor = FromJson(
R"json({"not_array": "Google", "array": [1, 2], "empty_array": []})json");
auto value20 = cbor::Parse(cbor);
ASSERT_TRUE(value20.ok());
auto result = std::move(value20).value();
auto value21 = result->AsMap();
ASSERT_TRUE(value21.ok());
auto map = std::move(value21).value();
EXPECT_THAT(map.GetOptionalArray("not_array"), Eq(std::nullopt));
EXPECT_THAT(map.GetOptionalArray("not_found"), Eq(std::nullopt));
EXPECT_TRUE(map.GetOptionalArray("array").has_value());
EXPECT_TRUE(map.GetOptionalArray("empty_array").has_value());
}
TEST(ParseTest, OptionalUint64) {
auto cbor = FromJson(R"json({"not_uint": "Google", "uint": 1})json");
auto value22 = cbor::Parse(cbor);
ASSERT_TRUE(value22.ok());
auto result = std::move(value22).value();
auto value23 = result->AsMap();
ASSERT_TRUE(value23.ok());
auto map = std::move(value23).value();
EXPECT_THAT(map.GetOptionalUint64("not_uint"), Eq(std::nullopt));
EXPECT_THAT(map.GetOptionalUint64("not_found"), Eq(std::nullopt));
EXPECT_THAT(map.GetOptionalUint64("uint").value(), Eq(1));
}
TEST(ParseTest, UintMapKeys) {
// FromJson doesn't support creating integer-keyed maps -- would need
// special support like the "b64'blah" bytestring hack because JSON doesn't
// support integer map keys. So we'll create the map directly using cppbor.
cppbor::Map orig_map;
orig_map.add(cppbor::Uint(0), cppbor::Bstr("zero"));
orig_map.add(cppbor::Uint(3), cppbor::Nint(-333));
orig_map.add(cppbor::Uint(10),
cppbor::Array(cppbor::Tstr("a"), cppbor::Nint(-12)));
auto bytes = orig_map.encode();
absl::string_view bytes_view(reinterpret_cast<char*>(bytes.data()),
bytes.size());
auto value24 = cbor::Parse(bytes_view);
ASSERT_TRUE(value24.ok());
auto result = std::move(value24).value();
auto value25 = result->AsMap();
ASSERT_TRUE(value25.ok());
auto map = std::move(value25).value();
EXPECT_THAT(map.GetByteString(0), IsOkAndHolds("zero"));
EXPECT_THAT(map.GetInt64(3), IsOkAndHolds(-333));
auto value26 = map.GetArray(10);
ASSERT_TRUE(value26.ok());
auto arr = std::move(value26).value();
EXPECT_THAT(arr.size(), Eq(2));
EXPECT_THAT(arr.GetString(0), IsOkAndHolds("a"));
EXPECT_THAT(arr.GetInt64(1), IsOkAndHolds(-12));
}
TEST(ParseTest, ErrorResultNotAMap) {
auto cbor = FromJson(R"json([1, 2, 3])json");
auto value27 = cbor::Parse(cbor);
ASSERT_TRUE(value27.ok());
auto result = std::move(value27).value();
EXPECT_THAT(result->AsMap(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("CBOR parsed result is not a map")));
}
TEST(ParseTest, ErrorResultNotAnArray) {
auto cbor = FromJson(R"json({"vendor": "Google", "schema": 0})json");
auto value28 = cbor::Parse(cbor);
ASSERT_TRUE(value28.ok());
auto result = std::move(value28).value();
EXPECT_THAT(result->AsArray(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("CBOR parsed result is not an array")));
}
TEST(ParseTest, ErrorMapGetStringNotAString) {
auto cbor = FromJson(R"json({"vendor": "Google", "schema": 0})json");
auto value29 = cbor::Parse(cbor);
ASSERT_TRUE(value29.ok());
auto result = std::move(value29).value();
auto value30 = result->AsMap();
ASSERT_TRUE(value30.ok());
auto map = std::move(value30).value();
EXPECT_THAT(map.GetString("schema"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("CBOR item is not a string")));
}
TEST(ParseTest, ErrorMapGetInt64NotAnInt) {
auto cbor = FromJson(R"json({"vendor": "Google", "schema": 0})json");
auto value31 = cbor::Parse(cbor);
ASSERT_TRUE(value31.ok());
auto result = std::move(value31).value();
auto value32 = result->AsMap();
ASSERT_TRUE(value32.ok());
auto map = std::move(value32).value();
EXPECT_THAT(map.GetInt64("vendor"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("CBOR item is not an int")));
}
TEST(ParseTest, ErrorMapGetUint64NotAUint) {
auto cbor = FromJson(R"json({"vendor": "Google", "schema": 0})json");
auto value33 = cbor::Parse(cbor);
ASSERT_TRUE(value33.ok());
auto result = std::move(value33).value();
auto value34 = result->AsMap();
ASSERT_TRUE(value34.ok());
auto map = std::move(value34).value();
EXPECT_THAT(map.GetUint64("vendor"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("CBOR item is not an uint")));
}
TEST(ParseTest, ErrorNotAUintInArray) {
auto cbor = FromJson(R"json(["vendor", "Google"])json");
auto value35 = cbor::Parse(cbor);
ASSERT_TRUE(value35.ok());
auto result = std::move(value35).value();
auto value36 = result->AsArray();
ASSERT_TRUE(value36.ok());
auto array = std::move(value36).value();
EXPECT_THAT(array.GetUint64(0),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("CBOR item is not an uint")));
}
TEST(ParseTest, ErrorNotAMapInArray) {
auto cbor = FromJson(R"json([{"vendor": "Google"}, "schema"])json");
auto value37 = cbor::Parse(cbor);
ASSERT_TRUE(value37.ok());
auto result = std::move(value37).value();
auto value38 = result->AsArray();
ASSERT_TRUE(value38.ok());
auto array = std::move(value38).value();
EXPECT_THAT(array.GetMap(1), StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("CBOR item is not a map")));
}
TEST(ParseTest, ErrorKeyNotFound) {
auto cbor = FromJson(R"json({"vendor": "Google", "schema": 0})json");
auto value39 = cbor::Parse(cbor);
ASSERT_TRUE(value39.ok());
auto result = std::move(value39).value();
auto value40 = result->AsMap();
ASSERT_TRUE(value40.ok());
auto map = std::move(value40).value();
EXPECT_THAT(
map.GetString("not_found"),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("Key not found")));
}
TEST(ParseTest, DuplicateKey) {
std::string cbor;
// This is a CBOR map {"color": "red", "color": "green"}.
ASSERT_TRUE(absl::HexStringToBytes(
"a265636f6c6f726372656465636f6c6f7265677265656e", &cbor));
auto value41 = cbor::Parse(cbor);
ASSERT_TRUE(value41.ok());
auto result = std::move(value41).value();
auto value42 = result->AsMap();
ASSERT_TRUE(value42.ok());
auto map = std::move(value42).value();
EXPECT_THAT(map.GetString("color"), "red");
}
TEST(ParseTest, ErrorIndexOutOfRange) {
auto cbor = FromJson(R"json([0, 1, 2])json");
auto value43 = cbor::Parse(cbor);
ASSERT_TRUE(value43.ok());
auto result = std::move(value43).value();
auto value44 = result->AsArray();
ASSERT_TRUE(value44.ok());
auto array = std::move(value44).value();
EXPECT_THAT(array.GetUint64(3), StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Index out of range")));
}
TEST(ParseTest, ErrorFailedToParse) {
auto cbor = FromJson(R"json({"vendor": "Google", "schema": 0})json");
EXPECT_THAT(cbor::Parse(cbor.substr(0, 10)),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("CBOR parsing failed")));
}
TEST(ParseTest, ErrorTrailingBytes) {
auto cbor = FromJson(R"json({"vendor": "Google", "schema": 0})json");
EXPECT_THAT(cbor::Parse(cbor + "trailing bytes"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Trailing bytes")));
}
void CheckMap(const cbor::MapView& map, absl::string_view str_key,
uint32_t int_key);
void CheckArray(const cbor::ArrayView& array, absl::string_view str_key,
uint32_t int_key) {
if (array.empty()) {
ASSERT_THAT(array.size(), Eq(0));
ASSERT_THAT(array.value_type(0), Eq(cbor::ItemView::Type::kUnknown));
} else {
ASSERT_THAT(array.size(), Gt(0));
}
for (auto i = 0; i < array.size(); ++i) {
SCOPED_TRACE(absl::StrCat(i));
switch (array.value_type(i)) {
case cbor::ItemView::Type::kUnknown:
EXPECT_THAT(array.GetBool(i),
StatusIs(absl::StatusCode::kInvalidArgument));
EXPECT_THAT(array.GetFloat(i),
StatusIs(absl::StatusCode::kInvalidArgument));
EXPECT_THAT(array.GetDouble(i),
StatusIs(absl::StatusCode::kInvalidArgument));
EXPECT_THAT(array.GetString(i),
StatusIs(absl::StatusCode::kInvalidArgument));
EXPECT_THAT(array.GetByteString(i),
StatusIs(absl::StatusCode::kInvalidArgument));
EXPECT_THAT(array.GetInt64(i),
StatusIs(absl::StatusCode::kInvalidArgument));
EXPECT_THAT(array.GetUint64(i),
StatusIs(absl::StatusCode::kInvalidArgument));
EXPECT_THAT(array.GetMap(i),
StatusIs(absl::StatusCode::kInvalidArgument));
EXPECT_THAT(array.GetArray(i),
StatusIs(absl::StatusCode::kInvalidArgument));
break;
case cbor::ItemView::Type::kBool:
EXPECT_THAT(array.GetBool(i), StatusIs(absl::StatusCode::kOk));
break;
case cbor::ItemView::Type::kFloat:
EXPECT_THAT(array.GetFloat(i), StatusIs(absl::StatusCode::kOk));
break;
case cbor::ItemView::Type::kDouble:
EXPECT_THAT(array.GetDouble(i), StatusIs(absl::StatusCode::kOk));
break;
case cbor::ItemView::Type::kString: {
auto value45 = array.GetString(i);
ASSERT_TRUE(value45.ok());
absl::string_view str = std::move(value45).value();
std::string force_copy(str);
break;
}
case cbor::ItemView::Type::kByteString: {
auto value46 = array.GetByteString(i);
ASSERT_TRUE(value46.ok());
absl::string_view str = std::move(value46).value();
std::string force_copy(str);
break;
}
case cbor::ItemView::Type::kInt64:
EXPECT_THAT(array.GetInt64(i), StatusIs(absl::StatusCode::kOk));
break;
case cbor::ItemView::Type::kUint64:
EXPECT_THAT(array.GetUint64(i), StatusIs(absl::StatusCode::kOk));
break;
case cbor::ItemView::Type::kMap: {
auto value47 = array.GetMap(i);
ASSERT_TRUE(value47.ok());
auto child = std::move(value47).value();
CheckMap(child, str_key, int_key);
break;
}
case cbor::ItemView::Type::kArray: {
auto value48 = array.GetArray(i);
ASSERT_TRUE(value48.ok());
auto child = std::move(value48).value();
CheckArray(child, str_key, int_key);
break;
}
}
}
}
void CheckMap(const cbor::MapView& map, absl::string_view str_key,
uint32_t int_key) {
{
// Bool:
const auto status_bool = map.GetBool(str_key);
const auto opt_bool = map.GetOptionalBool(str_key);
ASSERT_THAT(status_bool.ok(), Eq(opt_bool.has_value()));
if (status_bool.ok()) {
EXPECT_THAT(*status_bool, Eq(*opt_bool));
}
}
{
// String:
const auto status_str = map.GetString(str_key);
const auto opt_str = map.GetOptionalString(str_key);
ASSERT_THAT(status_str.ok(), Eq(opt_str.has_value()));
if (status_str.ok()) {
EXPECT_THAT(*status_str, Eq(*opt_str));
}
}
}
} // namespace