diff --git a/llvm/include/llvm/Support/JSON.h b/llvm/include/llvm/Support/JSON.h --- a/llvm/include/llvm/Support/JSON.h +++ b/llvm/include/llvm/Support/JSON.h @@ -145,6 +145,21 @@ json::Object *getObject(StringRef K); const json::Array *getArray(StringRef K) const; json::Array *getArray(StringRef K); + + /// \a llvm::Expected based accessors + /// In case of an error, they return a user-friendly message. + /// \{ + llvm::Expected getOrError(StringRef K) const; + llvm::Expected getIntegerOrError(StringRef K) const; + llvm::Expected getStringOrError(StringRef K) const; + llvm::Expected> + getOptionalStringOrError(StringRef K) const; + llvm::Expected getObjectOrError(StringRef K) const; + llvm::Expected getArrayOrError(StringRef K) const; + /// \} + +private: + llvm::Error createMissingKeyError(llvm::StringRef K) const; }; bool operator==(const Object &LHS, const Object &RHS); inline bool operator!=(const Object &LHS, const Object &RHS) { @@ -438,7 +453,18 @@ return LLVM_LIKELY(Type == T_Array) ? &as() : nullptr; } + /// \a llvm::Expected based accessors + /// In case of an error, they return a user-friendly message. + /// \{ + llvm::Expected getAsObjectOrError() const; + llvm::Expected getAsArrayOrError() const; + llvm::Expected getAsStringOrError() const; + llvm::Expected getAsIntegerOrError() const; + /// \} + private: + llvm::Error createWrongTypeError(llvm::StringRef Type) const; + void destroy(); void copyFrom(const Value &M); // We allow moving from *const* Values, by marking all members as mutable! diff --git a/llvm/lib/Support/JSON.cpp b/llvm/lib/Support/JSON.cpp --- a/llvm/lib/Support/JSON.cpp +++ b/llvm/lib/Support/JSON.cpp @@ -77,6 +77,62 @@ return V->getAsArray(); return nullptr; } + +llvm::Error Object::createMissingKeyError(llvm::StringRef K) const { + std::string S; + llvm::raw_string_ostream OS(S); + json::Object Obj = *this; + OS << llvm::formatv( + "JSON object is missing the \"{0}\" field.\nValue:\n{1:2}", K, + json::Value(std::move(Obj))); + OS.flush(); + + return llvm::createStringError(std::errc::invalid_argument, OS.str().c_str()); +} + +llvm::Expected Object::getOrError(StringRef K) const { + if (const json::Value *V = get(K)) + return V; + else + return createMissingKeyError(K); +} + +llvm::Expected Object::getIntegerOrError(StringRef K) const { + if (llvm::Expected V = getOrError(K)) + return V.get()->getAsIntegerOrError(); + else + return V.takeError(); +} + +llvm::Expected Object::getStringOrError(StringRef K) const { + if (llvm::Expected V = getOrError(K)) + return V.get()->getAsStringOrError(); + else + return V.takeError(); +} + +llvm::Expected Object::getArrayOrError(StringRef K) const { + if (llvm::Expected V = getOrError(K)) + return V.get()->getAsArrayOrError(); + else + return V.takeError(); +} + +llvm::Expected +Object::getObjectOrError(StringRef K) const { + if (llvm::Expected V = getOrError(K)) + return V.get()->getAsObjectOrError(); + else + return V.takeError(); +} + +llvm::Expected> +Object::getOptionalStringOrError(StringRef K) const { + if (const json::Value *V = get(K)) + return V->getAsStringOrError(); + return llvm::None; +} + bool operator==(const Object &LHS, const Object &RHS) { if (LHS.size() != RHS.size()) return false; @@ -150,6 +206,42 @@ } } +llvm::Error Value::createWrongTypeError(llvm::StringRef Type) const { + std::string S; + llvm::raw_string_ostream OS(S); + OS << llvm::formatv("JSON value is expected to be \"{0}\".\nValue:\n{1:2}", + Type, *this); + OS.flush(); + + return llvm::createStringError(std::errc::invalid_argument, OS.str().c_str()); +} + +llvm::Expected Value::getAsIntegerOrError() const { + llvm::Optional V = getAsInteger(); + if (V.hasValue()) + return *V; + return createWrongTypeError("integer"); +} + +llvm::Expected Value::getAsStringOrError() const { + llvm::Optional V = getAsString(); + if (V.hasValue()) + return *V; + return createWrongTypeError("string"); +} + +llvm::Expected Value::getAsArrayOrError() const { + if (const json::Array *V = getAsArray()) + return V; + return createWrongTypeError("array"); +} + +llvm::Expected Value::getAsObjectOrError() const { + if (const json::Object *V = getAsObject()) + return V; + return createWrongTypeError("object"); +} + void Value::destroy() { switch (Type) { case T_Null: @@ -715,4 +807,3 @@ llvm_unreachable("json::Value format options should be an integer"); json::OStream(OS, IndentAmount).value(E); } - diff --git a/llvm/unittests/Support/JSONTest.cpp b/llvm/unittests/Support/JSONTest.cpp --- a/llvm/unittests/Support/JSONTest.cpp +++ b/llvm/unittests/Support/JSONTest.cpp @@ -189,6 +189,119 @@ Compare("\r[\n\t] ", {}); } +TEST(JSONTest, ExpectedGettersForObject) { + Object O{{"a", 1}, + {"b", "2"}, + {"array", {1, 2, 3}}, + {"object", Object({{"key", "value"}})}}; + + // Testing valid keys + if (auto E = O.getOrError("a")) + EXPECT_EQ(E.get()->kind(), llvm::json::Value::Kind::Number); + else + FAIL() << "Unexpected error"; + + if (auto E = O.getIntegerOrError("a")) + EXPECT_EQ(*E, 1); + else + FAIL() << "Unexpected error"; + + if (auto E = O.getStringOrError("b")) + EXPECT_EQ(*E, "2"); + else + FAIL() << "Unexpected error"; + + if (auto E = O.getOptionalStringOrError("c")) + EXPECT_EQ(*E, llvm::None); + else + FAIL() << "Unexpected error"; + + if (auto E = O.getArrayOrError("array")) + EXPECT_EQ(E.get()->size(), (size_t)3); + else + FAIL() << "Unexpected error"; + + if (auto E = O.getObjectOrError("object")) + EXPECT_EQ(E.get()->size(), (size_t)1); + else + FAIL() << "Unexpected error"; + + // Testing invalid keys + if (auto E = O.getOrError("a2")) + FAIL() << "Unexpected error"; + else + handleAllErrors(E.takeError(), [](const llvm::ErrorInfoBase &E) { + EXPECT_EQ(E.message(), + std::string(R"(JSON object is missing the "a2" field. +Value: +{ + "a": 1, + "array": [ + 1, + 2, + 3 + ], + "b": "2", + "object": { + "key": "value" + } +})")); + }); + + if (auto E = O.getIntegerOrError("array")) + FAIL() << "Unexpected error"; + else + handleAllErrors(E.takeError(), [](const llvm::ErrorInfoBase &E) { + EXPECT_EQ(E.message(), + std::string(R"(JSON value is expected to be "integer". +Value: +[ + 1, + 2, + 3 +])")); + }); +} + +TEST(JSONTest, ExpectedConvertersForObject) { + Value V(Object({{"key", "value"}})); + + if (auto E = V.getAsObjectOrError()) + EXPECT_EQ(E.get()->size(), (size_t)1); + else + FAIL() << "Unexpected error"; + + if (auto E = V.getAsIntegerOrError()) + FAIL() << "Unexpected error"; + else + handleAllErrors(E.takeError(), [](const llvm::ErrorInfoBase &E) { + EXPECT_EQ(E.message(), + std::string(R"(JSON value is expected to be "integer". +Value: +{ + "key": "value" +})")); + }); + + Value V2("string"); + if (auto E = V2.getAsStringOrError()) + EXPECT_EQ(*E, "string"); + else + FAIL() << "Unexpected error"; + + Value V3({1, 2, 3}); + if (auto E = V3.getAsArrayOrError()) + EXPECT_EQ(E.get()->size(), (size_t)3); + else + FAIL() << "Unexpected error"; + + Value V4(52); + if (auto E = V4.getAsIntegerOrError()) + EXPECT_EQ(E.get(), 52); + else + FAIL() << "Unexpected error"; +} + TEST(JSONTest, ParseErrors) { auto ExpectErr = [](llvm::StringRef Msg, llvm::StringRef S) { if (auto E = parse(S)) {