Skip to content

Commit b6446a8

Browse files
authored
Test for JSON & CBOR encoding error handling (#432)
1 parent 778cd54 commit b6446a8

File tree

5 files changed

+169
-13
lines changed

5 files changed

+169
-13
lines changed

include/rfl/cbor/read.hpp

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,31 @@ using InputVarType = typename Reader::InputVarType;
2020
/// Parses an object from CBOR using reflection.
2121
template <class T, class... Ps>
2222
Result<internal::wrap_in_rfl_array_t<T>> read(const std::vector<char>& _bytes) {
23-
auto val = jsoncons::cbor::decode_cbor<jsoncons::json>(_bytes);
24-
auto r = Reader();
25-
return Parser<T, Processors<Ps...>>::read(r, InputVarType{&val});
23+
// TODO: Use a non-throwing decode_cbor(), pending https://github.com/danielaparker/jsoncons/issues/615
24+
try {
25+
auto val = jsoncons::cbor::decode_cbor<jsoncons::json>(_bytes);
26+
auto r = Reader();
27+
return Parser<T, Processors<Ps...>>::read(r, InputVarType{&val});
28+
} catch(const jsoncons::ser_error& e) {
29+
std::string error("Could not parse CBOR: ");
30+
error.append(e.what());
31+
return rfl::error(error);
32+
}
2633
}
2734

2835
/// Parses an object from a stream.
2936
template <class T, class... Ps>
3037
Result<internal::wrap_in_rfl_array_t<T>> read(std::istream& _stream) {
31-
auto val = jsoncons::cbor::decode_cbor<jsoncons::json>(_stream);
32-
auto r = Reader();
33-
return Parser<T, Processors<Ps...>>::read(r, InputVarType{&val});
38+
// TODO: Use a non-throwing decode_cbor(), pending https://github.com/danielaparker/jsoncons/issues/615
39+
try {
40+
auto val = jsoncons::cbor::decode_cbor<jsoncons::json>(_stream);
41+
auto r = Reader();
42+
return Parser<T, Processors<Ps...>>::read(r, InputVarType{&val});
43+
} catch(const jsoncons::ser_error& e) {
44+
std::string error("Could not parse CBOR: ");
45+
error.append(e.what());
46+
return rfl::error(error);
47+
}
3448
}
3549

3650
} // namespace rfl::cbor

include/rfl/ubjson/read.hpp

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,31 @@ using InputVarType = typename Reader::InputVarType;
2020
/// Parses an object from UBJSON using reflection.
2121
template <class T, class... Ps>
2222
Result<internal::wrap_in_rfl_array_t<T>> read(const std::vector<char>& _bytes) {
23-
auto val = jsoncons::ubjson::decode_ubjson<jsoncons::json>(_bytes);
24-
auto r = Reader();
25-
return Parser<T, Processors<Ps...>>::read(r, InputVarType{&val});
23+
// TODO: Use a non-throwing decode_ubjson(), pending https://github.com/danielaparker/jsoncons/issues/615
24+
try {
25+
auto val = jsoncons::ubjson::decode_ubjson<jsoncons::json>(_bytes);
26+
auto r = Reader();
27+
return Parser<T, Processors<Ps...>>::read(r, InputVarType{&val});
28+
} catch(const jsoncons::ser_error& e) {
29+
std::string error("Could not parse UBJSON: ");
30+
error.append(e.what());
31+
return rfl::error(error);
32+
}
2633
}
2734

2835
/// Parses an object from a stream.
2936
template <class T, class... Ps>
3037
Result<internal::wrap_in_rfl_array_t<T>> read(std::istream& _stream) {
31-
auto val = jsoncons::ubjson::decode_ubjson<jsoncons::json>(_stream);
32-
auto r = Reader();
33-
return Parser<T, Processors<Ps...>>::read(r, InputVarType{&val});
38+
// TODO: Use a non-throwing decode_ubjson(), pending https://github.com/danielaparker/jsoncons/issues/615
39+
try {
40+
auto val = jsoncons::ubjson::decode_ubjson<jsoncons::json>(_stream);
41+
auto r = Reader();
42+
return Parser<T, Processors<Ps...>>::read(r, InputVarType{&val});
43+
} catch(const jsoncons::ser_error& e) {
44+
std::string error("Could not parse UBJSON: ");
45+
error.append(e.what());
46+
return rfl::error(error);
47+
}
3448
}
3549

3650
} // namespace rfl::ubjson

tests/cbor/test_error_messages.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#include <gtest/gtest.h>
2+
3+
#include <iostream>
4+
#include <rfl.hpp>
5+
#include <rfl/cbor.hpp>
6+
#include <rfl/json.hpp>
7+
#include <string>
8+
#include <vector>
9+
10+
namespace test_error_messages {
11+
12+
struct Person {
13+
rfl::Rename<"firstName", std::string> first_name;
14+
rfl::Rename<"lastName", std::string> last_name;
15+
rfl::Timestamp<"%Y-%m-%d"> birthday;
16+
std::vector<Person> children;
17+
};
18+
19+
TEST(cbor, test_field_error_messages) {
20+
const std::string faulty_string =
21+
R"({"firstName":"Homer","lastName":12345,"birthday":"04/19/1987"})";
22+
const auto faulty_generic = rfl::json::read<rfl::Generic>(faulty_string);
23+
const auto faulty_cbor = rfl::cbor::write(faulty_generic);
24+
const auto result = rfl::cbor::read<Person>(faulty_cbor);
25+
26+
// Order of errors is different than input JSON because rfl::Generic doesn't preserve order
27+
const std::string expected = R"(Found 3 errors:
28+
1) Failed to parse field 'birthday': String '04/19/1987' did not match format '%Y-%m-%d'.
29+
2) Failed to parse field 'lastName': Could not cast to string.
30+
3) Field named 'children' not found.)";
31+
32+
EXPECT_TRUE(!result.has_value() && true);
33+
34+
EXPECT_EQ(result.error().what(), expected);
35+
}
36+
37+
TEST(cbor, test_decode_error_without_exception) {
38+
const std::string good_string =
39+
R"({"firstName":"Homer","lastName":"Simpson","birthday":"1987-04-19"})";
40+
const auto good_generic = rfl::json::read<rfl::Generic>(good_string);
41+
auto faulty_cbor = rfl::cbor::write(good_generic);
42+
faulty_cbor[1] = '\xff'; // Corrupt structure of CBOR encoding
43+
44+
rfl::Result<Person> result = rfl::error("result didn't get set");
45+
EXPECT_NO_THROW({
46+
result = rfl::cbor::read<Person>(faulty_cbor);
47+
});
48+
49+
// A proposal: A generic prefix, followed by the underlying library's error output
50+
const std::string expected = R"(Could not parse CBOR: An unknown type was found in the stream at position 1)";
51+
EXPECT_EQ(result.error().what(), expected);
52+
53+
EXPECT_TRUE(!result.has_value() && true);
54+
}
55+
56+
} // namespace test_error_messages

tests/json/test_error_messages.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ struct Person {
1515
std::vector<Person> children;
1616
};
1717

18-
TEST(json, test_error_messages) {
18+
TEST(json, test_field_error_messages) {
1919
const std::string faulty_string =
2020
R"({"firstName":"Homer","lastName":12345,"birthday":"04/19/1987"})";
2121

@@ -30,4 +30,20 @@ TEST(json, test_error_messages) {
3030

3131
EXPECT_EQ(result.error().what(), expected);
3232
}
33+
34+
TEST(json, test_decode_error_without_exception) {
35+
// Note using valid field names, but invalid JSON delimiter ';'
36+
const std::string faulty_string =
37+
R"({"firstName":"Homer";"lastName":"Simpson";"birthday":"1987-04-19"})";
38+
39+
rfl::Result<Person> result = rfl::error("result didn't get set");
40+
EXPECT_NO_THROW({
41+
result = rfl::json::read<Person>(faulty_string);
42+
});
43+
44+
EXPECT_TRUE(!result.has_value() && true);
45+
46+
EXPECT_EQ(result.error().what(), "Could not parse document");
47+
}
48+
3349
} // namespace test_error_messages

tests/ubjson/test_error_messages.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#include <gtest/gtest.h>
2+
3+
#include <iostream>
4+
#include <rfl.hpp>
5+
#include <rfl/ubjson.hpp>
6+
#include <rfl/json.hpp>
7+
#include <string>
8+
#include <vector>
9+
10+
namespace test_error_messages {
11+
12+
struct Person {
13+
rfl::Rename<"firstName", std::string> first_name;
14+
rfl::Rename<"lastName", std::string> last_name;
15+
rfl::Timestamp<"%Y-%m-%d"> birthday;
16+
std::vector<Person> children;
17+
};
18+
19+
TEST(ubjson, test_field_error_messages) {
20+
const std::string faulty_string =
21+
R"({"firstName":"Homer","lastName":12345,"birthday":"04/19/1987"})";
22+
const auto faulty_generic = rfl::json::read<rfl::Generic>(faulty_string);
23+
const auto faulty_ubjson = rfl::ubjson::write(faulty_generic);
24+
const auto result = rfl::ubjson::read<Person>(faulty_ubjson);
25+
26+
// Order of errors is different than input JSON because rfl::Generic doesn't preserve order
27+
const std::string expected = R"(Found 3 errors:
28+
1) Failed to parse field 'birthday': String '04/19/1987' did not match format '%Y-%m-%d'.
29+
2) Failed to parse field 'lastName': Could not cast to string.
30+
3) Field named 'children' not found.)";
31+
32+
EXPECT_TRUE(!result.has_value() && true);
33+
34+
EXPECT_EQ(result.error().what(), expected);
35+
}
36+
37+
TEST(ubjson, test_decode_error_without_exception) {
38+
const std::string good_string =
39+
R"({"firstName":"Homer","lastName":"Simpson","birthday":"1987-04-19"})";
40+
const auto good_generic = rfl::json::read<rfl::Generic>(good_string);
41+
auto faulty_ubjson = rfl::ubjson::write(good_generic);
42+
faulty_ubjson[0] = '\xff'; // Corrupt structure of ubjson encoding
43+
44+
rfl::Result<Person> result = rfl::error("result didn't get set");
45+
EXPECT_NO_THROW({
46+
result = rfl::ubjson::read<Person>(faulty_ubjson);
47+
});
48+
49+
// A proposal: A generic prefix, followed by the underlying library's error output
50+
const std::string expected = R"(Could not parse UBJSON: Unknown type at position 1)";
51+
EXPECT_EQ(result.error().what(), expected);
52+
53+
EXPECT_TRUE(!result.has_value() && true);
54+
}
55+
56+
} // namespace test_error_messages

0 commit comments

Comments
 (0)