1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-11-22 18:54:02 +01:00

[JSON] Add ObjectMapper::mapOptional to validate optional data.

Currently the idiom for mapping optional fields is:
  ObjectMapper O(Val, P);
  if (!O.map("required1", Out.R1) || !O.map("required2", Out.R2))
    return false;
  O.map("optional1", Out.O1); // ignore result
  return true;

If `optional1` is present but malformed, then we won't detect/report
that error. We may even leave `Out` in an incomplete state while returning true.
Instead, we'd often prefer to ignore `optional1` if it is absent, but otherwise
behave just like map().

Differential Revision: https://reviews.llvm.org/D89128
This commit is contained in:
Sam McCall 2020-10-09 15:33:56 +02:00
parent 030fbbba7c
commit 4c407017f3
2 changed files with 28 additions and 24 deletions

View File

@ -741,10 +741,9 @@ template <typename T> Value toJSON(const llvm::Optional<T> &Opt) {
/// \code /// \code
/// bool fromJSON(const Value &E, MyStruct &R, Path P) { /// bool fromJSON(const Value &E, MyStruct &R, Path P) {
/// ObjectMapper O(E, P); /// ObjectMapper O(E, P);
/// if (!O || !O.map("mandatory_field", R.MandatoryField)) /// // When returning false, error details were already reported.
/// return false; // error details are already reported /// return O && O.map("mandatory_field", R.MandatoryField) &&
/// O.map("optional_field", R.OptionalField); /// O.mapOptional("optional_field", R.OptionalField);
/// return true;
/// } /// }
/// \endcode /// \endcode
class ObjectMapper { class ObjectMapper {
@ -780,6 +779,16 @@ public:
return true; return true;
} }
/// Maps a property to a field, if it exists.
/// If the property exists and is invalid, reports an error.
/// If the property does not exist, Out is unchanged.
template <typename T> bool mapOptional(StringLiteral Prop, T &Out) {
assert(*this && "Must check this is an object before calling map()");
if (const Value *E = O->get(Prop))
return fromJSON(*E, Out, P.field(Prop));
return true;
}
private: private:
const Object *O; const Object *O;
Path P; Path P;

View File

@ -375,10 +375,8 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
} }
bool fromJSON(const Value &E, CustomStruct &R, Path P) { bool fromJSON(const Value &E, CustomStruct &R, Path P) {
ObjectMapper O(E, P); ObjectMapper O(E, P);
if (!O || !O.map("str", R.S) || !O.map("int", R.I)) return O && O.map("str", R.S) && O.map("int", R.I) &&
return false; O.mapOptional("bool", R.B);
O.map("bool", R.B);
return true;
} }
static std::string errorContext(const Value &V, const Path::Root &R) { static std::string errorContext(const Value &V, const Path::Root &R) {
@ -392,24 +390,18 @@ TEST(JSONTest, Deserialize) {
std::map<std::string, std::vector<CustomStruct>> R; std::map<std::string, std::vector<CustomStruct>> R;
CustomStruct ExpectedStruct = {"foo", 42, true}; CustomStruct ExpectedStruct = {"foo", 42, true};
std::map<std::string, std::vector<CustomStruct>> Expected; std::map<std::string, std::vector<CustomStruct>> Expected;
Value J = Object{ Value J = Object{{"foo", Array{
{"foo", Object{
Array{ {"str", "foo"},
Object{ {"int", 42},
{"str", "foo"}, {"bool", true},
{"int", 42}, {"unknown", "ignored"},
{"bool", true}, },
{"unknown", "ignored"}, Object{{"str", "bar"}},
}, }}};
Object{{"str", "bar"}},
Object{
{"str", "baz"}, {"bool", "string"}, // OK, deserialize ignores.
},
}}};
Expected["foo"] = { Expected["foo"] = {
CustomStruct("foo", 42, true), CustomStruct("foo", 42, true),
CustomStruct("bar", llvm::None, false), CustomStruct("bar", llvm::None, false),
CustomStruct("baz", llvm::None, false),
}; };
Path::Root Root("CustomStruct"); Path::Root Root("CustomStruct");
ASSERT_TRUE(fromJSON(J, R, Root)); ASSERT_TRUE(fromJSON(J, R, Root));
@ -423,7 +415,6 @@ TEST(JSONTest, Deserialize) {
"foo": [ "foo": [
/* error: expected object */ /* error: expected object */
123, 123,
{ ... },
{ ... } { ... }
] ]
})"; })";
@ -443,6 +434,10 @@ TEST(JSONTest, Deserialize) {
// Optional<T> must parse as the correct type if present. // Optional<T> must parse as the correct type if present.
EXPECT_FALSE(fromJSON(Object{{"str", "1"}, {"int", "string"}}, V, Root)); EXPECT_FALSE(fromJSON(Object{{"str", "1"}, {"int", "string"}}, V, Root));
EXPECT_EQ("expected integer at CustomStruct.int", toString(Root.getError())); EXPECT_EQ("expected integer at CustomStruct.int", toString(Root.getError()));
// mapOptional must parse as the correct type if present.
EXPECT_FALSE(fromJSON(Object{{"str", "1"}, {"bool", "string"}}, V, Root));
EXPECT_EQ("expected boolean at CustomStruct.bool", toString(Root.getError()));
} }
TEST(JSONTest, ParseDeserialize) { TEST(JSONTest, ParseDeserialize) {