Mercurial > malikania
changeset 88:469b6d558ab0
Common: add util::json functions, closes #628
author | David Demelier <markand@malikania.fr> |
---|---|
date | Sat, 04 Feb 2017 16:15:20 +0100 |
parents | 955a9409ab5f |
children | 0bedc450a9d2 |
files | libclient/malikania/client/loader.cpp libcommon/malikania/util.cpp libcommon/malikania/util.hpp tests/libcommon/util/main.cpp |
diffstat | 4 files changed, 514 insertions(+), 35 deletions(-) [+] |
line wrap: on
line diff
--- a/libclient/malikania/client/loader.cpp Sat Feb 04 14:40:20 2017 +0100 +++ b/libclient/malikania/client/loader.cpp Sat Feb 04 16:15:20 2017 +0100 @@ -29,7 +29,7 @@ using boost::str; using boost::format; -using mlk::util::json::invalid; +using namespace nlohmann; namespace mlk { @@ -110,11 +110,7 @@ auto sprite = load_sprite(require_string(id, value, "sprite")); // Load all frames. - auto property = value["frames"]; - - if (!property.is_array()) { - throw invalid(id, nlohmann::json::value_t::array); - } + auto property = util::json::require_array(value, "/frames"_json_pointer); animation_frames frames; int index = 0;
--- a/libcommon/malikania/util.cpp Sat Feb 04 14:40:20 2017 +0100 +++ b/libcommon/malikania/util.cpp Sat Feb 04 16:15:20 2017 +0100 @@ -16,6 +16,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <cassert> #include <string> #include <stdexcept> @@ -263,38 +264,73 @@ return result; } +/* + * json utilities + * ------------------------------------------------------------------ + */ + namespace json { -std::runtime_error invalid(const std::string& name, const nlohmann::json::value_t type) +/* + * XXX: until json get a non-throwing function to check if a pointer exists, + * use this to check for a value at the given pointer or throw a + * property_error. + */ +nlohmann::json require(const nlohmann::json& object, + const nlohmann::json::json_pointer& pointer, + const nlohmann::json::value_t expected) { - std::ostringstream oss; - - oss << "invalid '" << name << "' property ('"; + assert(object.is_object()); - switch (type) { - case nlohmann::json::value_t::null: - oss << "null"; - break; - case nlohmann::json::value_t::object: - oss << "object"; - break; - case nlohmann::json::value_t::array: - oss << "array"; - break; - case nlohmann::json::value_t::string: - oss << "string"; - break; - case nlohmann::json::value_t::boolean: - oss << "boolean"; - break; - default: - oss << "number"; - break; + nlohmann::json value; + + try { + value = object.at(pointer); + } catch (...) { + throw property_error(object, pointer, nlohmann::json::value_t::null, expected); + } + + if (value.type() != expected) { + throw property_error(object, pointer, value.type(), expected); } - oss << " expected)"; + return value; +} + +nlohmann::json require_array(const nlohmann::json& object, + const nlohmann::json::json_pointer& pointer) +{ + return require(object, pointer, nlohmann::json::value_t::array); +} + +bool require_bool(const nlohmann::json& object, + const nlohmann::json::json_pointer& pointer) +{ + return require(object, pointer, nlohmann::json::value_t::boolean); +} - return std::runtime_error(oss.str()); +int require_int(const nlohmann::json& object, + const nlohmann::json::json_pointer& pointer) +{ + return require(object, pointer, nlohmann::json::value_t::number_integer); +} + +nlohmann::json require_object(const nlohmann::json& object, + const nlohmann::json::json_pointer& pointer) +{ + return require(object, pointer, nlohmann::json::value_t::object); +} + +std::string require_string(const nlohmann::json& object, + const nlohmann::json::json_pointer& pointer) +{ + return require(object, pointer, nlohmann::json::value_t::string); +} + +unsigned require_uint(const nlohmann::json& object, + const nlohmann::json::json_pointer& pointer) +{ + return require(object, pointer, nlohmann::json::value_t::number_unsigned); } } // !json
--- a/libcommon/malikania/util.hpp Sat Feb 04 14:40:20 2017 +0100 +++ b/libcommon/malikania/util.hpp Sat Feb 04 16:15:20 2017 +0100 @@ -25,7 +25,7 @@ */ #include <algorithm> -#include <stdexcept> +#include <exception> #include <string> #include <vector> @@ -94,11 +94,179 @@ namespace json { /** - * Create an exception with a message like: + * \brief Indicates a property error in an object. + */ +class property_error : public std::exception { +private: + nlohmann::json m_object; + nlohmann::json::json_pointer m_pointer; + nlohmann::json::value_t m_type; + nlohmann::json::value_t m_expected; + std::string m_message; + +public: + /** + * Construct the property error. + * + * \param object the faulty object + * \param pointer the property path + * \param type the found type + * \param expected the expected type + */ + inline property_error(nlohmann::json object, + nlohmann::json::json_pointer pointer, + nlohmann::json::value_t type, + nlohmann::json::value_t expected) noexcept + : m_object(std::move(object)) + , m_pointer(std::move(pointer)) + , m_type(type) + , m_expected(expected) + { + m_message += "invalid '" + m_pointer.to_string() + "' property "; + m_message += "(" + nlohmann::json(expected).type_name(); + m_message += " expected, got "; + m_message += nlohmann::json(type).type_name() + ")"; + } + + /** + * Get the faulty object. + * + * return the object + */ + inline const nlohmann::json& object() const noexcept + { + return m_object; + } + + /** + * Get the pointer to the property. + * + * \return the pointer + */ + inline const nlohmann::json::json_pointer& pointer() const noexcept + { + return m_pointer; + } + + /** + * Get the actual type. + * + * \return the type + */ + inline nlohmann::json::value_t type() const noexcept + { + return m_type; + } + + /** + * Get the expected type. + * + * \return the type + */ + inline nlohmann::json::value_t expected() const noexcept + { + return m_expected; + } + + /** + * Get a human formatted message. + * + * \return the message + */ + const char* what() const noexcept + { + return m_message.c_str(); + } +}; + +/** + * Require a value at the given pointer, if the value does not match the + * expected type, an exception is thrown. * - * 'invalid '<name>' property ('<type>' expected) + * If the property does not exists, a property_error with a null found type is + * thrown. + * + * \pre object.is_object() + * \param object the object + * \param pointer the pointer to the property + * \param expected the expected type + * \throw property_error on errors + */ +nlohmann::json require(const nlohmann::json& object, + const nlohmann::json::json_pointer& pointer, + const nlohmann::json::value_t expected); + +/** + * Require an array at the given pointer. + * + * \pre object.is_object() + * \param object the object + * \param pointer the pointer to the property + * \return the value + * \throw property_error on errors + */ +nlohmann::json require_array(const nlohmann::json& object, + const nlohmann::json::json_pointer& pointer); + +/** + * Require a boolean at the given pointer. + * + * \pre object.is_object() + * \param object the object + * \param pointer the pointer to the property + * \return the value + * \throw property_error on errors */ -std::runtime_error invalid(const std::string& name, const nlohmann::json::value_t type); +bool require_bool(const nlohmann::json& object, + const nlohmann::json::json_pointer& pointer); + +/** + * Require an integer at the given pointer. + * + * \pre object.is_object() + * \param object the object + * \param pointer the pointer to the property + * \return the value + * \throw property_error on errors + */ +int require_int(const nlohmann::json& object, + const nlohmann::json::json_pointer& pointer); + +/** + * Require an object at the given pointer. + * + * \pre object.is_object() + * \param object the object + * \param pointer the pointer to the property + * \return the value + * \throw property_error on errors + */ +nlohmann::json require_object(const nlohmann::json& object, + const nlohmann::json::json_pointer& pointer); + +/** + * Require a string at the given pointer. + * + * \pre object.is_object() + * \param object the object + * \param pointer the pointer to the property + * \return the value + * \throw property_error on errors + */ +std::string require_string(const nlohmann::json& object, + const nlohmann::json::json_pointer& pointer); + +/** + * Require an unsigned integer at the given pointer. + * + * \pre object.is_object() + * \param object the object + * \param pointer the pointer to the property + * \return the value + * \throw property_error on errors + */ +unsigned require_uint(const nlohmann::json& object, + const nlohmann::json::json_pointer& pointer); } // !json
--- a/tests/libcommon/util/main.cpp Sat Feb 04 14:40:20 2017 +0100 +++ b/tests/libcommon/util/main.cpp Sat Feb 04 16:15:20 2017 +0100 @@ -22,6 +22,21 @@ #include <malikania/util.hpp> using namespace mlk; +using namespace nlohmann; + +namespace nlohmann { + +namespace detail { + +std::ostream& operator<<(std::ostream& out, json::value_t type) +{ + out << json(type).type_name(); + return out; +} + +} // !detail + +} // !nlohmann /* * util::clamp @@ -56,3 +71,267 @@ } BOOST_AUTO_TEST_SUITE_END() + +/* + * util::json::require + * ------------------------------------------------------------------ + */ + +BOOST_AUTO_TEST_SUITE(json_require) + +BOOST_AUTO_TEST_CASE(simple) +{ + json object{ + { "b", true }, + { "i", 123 }, + { "s", "blabla" } + }; + + BOOST_REQUIRE_EQUAL(util::json::require( + object, "/b"_json_pointer, json::value_t::boolean).type(), json::value_t::boolean); + BOOST_REQUIRE_EQUAL(util::json::require( + object, "/i"_json_pointer, json::value_t::number_integer).type(), json::value_t::number_integer); + BOOST_REQUIRE_EQUAL(util::json::require( + object, "/s"_json_pointer, json::value_t::string).type(), json::value_t::string); +} + +BOOST_AUTO_TEST_CASE(nonexistent) +{ + auto json = json::object(); + + try { + util::json::require(json, "/non-existent"_json_pointer, json::value_t::string); + BOOST_FAIL("exception expected"); + } catch (const util::json::property_error& ex) { + BOOST_REQUIRE_EQUAL(ex.type(), json::value_t::null); + BOOST_REQUIRE_EQUAL(ex.expected(), json::value_t::string); + } +} + +BOOST_AUTO_TEST_CASE(invalid) +{ + json object{ + { "not-string", 123 } + }; + + try { + util::json::require(object, "/not-string"_json_pointer, json::value_t::string); + BOOST_FAIL("exception expected"); + } catch (const util::json::property_error& ex) { + BOOST_REQUIRE_EQUAL(ex.type(), json::value_t::number_integer); + BOOST_REQUIRE_EQUAL(ex.expected(), json::value_t::string); + } +} + +BOOST_AUTO_TEST_SUITE_END() + +/* + * util::json::require_array + * ------------------------------------------------------------------ + */ + +BOOST_AUTO_TEST_SUITE(json_require_array) + +BOOST_AUTO_TEST_CASE(simple) +{ + json object{ + { "a", { 1, 2, 3 } }, + { "l1", { + { "l2", { 4, 5, 6 } } + } + } + }; + + auto a = util::json::require_array(object, "/a"_json_pointer); + auto l2 = util::json::require_array(object, "/l1/l2"_json_pointer); + + BOOST_REQUIRE(a.is_array()); + BOOST_REQUIRE(l2.is_array()); +} + +BOOST_AUTO_TEST_CASE(invalid) +{ + json object{ + { "not-array", 123 } + }; + + try { + util::json::require_array(object, "/not-array"_json_pointer); + BOOST_FAIL("exception expected"); + } catch (const util::json::property_error& ex) { + BOOST_REQUIRE_EQUAL(ex.type(), json::value_t::number_integer); + BOOST_REQUIRE_EQUAL(ex.expected(), json::value_t::array); + } +} + +BOOST_AUTO_TEST_SUITE_END() + +/* + * util::json::require_bool + * ------------------------------------------------------------------ + */ + +BOOST_AUTO_TEST_SUITE(json_require_bool) + +BOOST_AUTO_TEST_CASE(simple) +{ + json object{ + { "b", true } + }; + + BOOST_REQUIRE_EQUAL(util::json::require_bool(object, "/b"_json_pointer), true); +} + +BOOST_AUTO_TEST_CASE(invalid) +{ + json object{ + { "not-bool", 123 } + }; + + try { + util::json::require_bool(object, "/not-bool"_json_pointer); + BOOST_FAIL("exception expected"); + } catch (const util::json::property_error& ex) { + BOOST_REQUIRE_EQUAL(ex.type(), json::value_t::number_integer); + BOOST_REQUIRE_EQUAL(ex.expected(), json::value_t::boolean); + } +} + +BOOST_AUTO_TEST_SUITE_END() + +/* + * util::json::require_int + * ------------------------------------------------------------------ + */ + +BOOST_AUTO_TEST_SUITE(json_require_int) + +BOOST_AUTO_TEST_CASE(simple) +{ + json object{ + { "i", 123 } + }; + + BOOST_REQUIRE_EQUAL(util::json::require_int(object, "/i"_json_pointer), 123); +} + +BOOST_AUTO_TEST_CASE(invalid) +{ + json object{ + { "not-int", true } + }; + + try { + util::json::require_int(object, "/not-int"_json_pointer); + BOOST_FAIL("exception expected"); + } catch (const util::json::property_error& ex) { + BOOST_REQUIRE_EQUAL(ex.type(), json::value_t::boolean); + BOOST_REQUIRE_EQUAL(ex.expected(), json::value_t::number_integer); + } +} + +BOOST_AUTO_TEST_SUITE_END() + +/* + * util::json::require_object + * ------------------------------------------------------------------ + */ + +BOOST_AUTO_TEST_SUITE(json_require_object) + +BOOST_AUTO_TEST_CASE(simple) +{ + json object{ + { + "network", json::object({ + { "host", "localhost" }, + { "port", 9090 } + }) + } + }; + + BOOST_REQUIRE(util::json::require_object(object, "/network"_json_pointer).is_object()); +} + +BOOST_AUTO_TEST_CASE(invalid) +{ + json object{ + { "not-object", 123 } + }; + + try { + util::json::require_object(object, "/not-object"_json_pointer); + BOOST_FAIL("exception expected"); + } catch (const util::json::property_error& ex) { + BOOST_REQUIRE_EQUAL(ex.type(), json::value_t::number_integer); + BOOST_REQUIRE_EQUAL(ex.expected(), json::value_t::object); + } +} + +BOOST_AUTO_TEST_SUITE_END() + +/* + * util::json::require_string + * ------------------------------------------------------------------ + */ + +BOOST_AUTO_TEST_SUITE(json_require_string) + +BOOST_AUTO_TEST_CASE(simple) +{ + json object{ + { "s", "hello" } + }; + + BOOST_REQUIRE_EQUAL(util::json::require_string(object, "/s"_json_pointer), "hello"); +} + +BOOST_AUTO_TEST_CASE(invalid) +{ + json object{ + { "not-string", 123 } + }; + + try { + util::json::require_string(object, "/not-string"_json_pointer); + BOOST_FAIL("exception expected"); + } catch (const util::json::property_error& ex) { + BOOST_REQUIRE_EQUAL(ex.type(), json::value_t::number_integer); + BOOST_REQUIRE_EQUAL(ex.expected(), json::value_t::string); + } +} + +BOOST_AUTO_TEST_SUITE_END() + +/* + * util::json::require_uint + * ------------------------------------------------------------------ + */ + +BOOST_AUTO_TEST_SUITE(json_require_uint) + +BOOST_AUTO_TEST_CASE(simple) +{ + json object{ + { "u1", 123U } + }; + + BOOST_REQUIRE_EQUAL(util::json::require_uint(object, "/u1"_json_pointer), 123U); +} + +BOOST_AUTO_TEST_CASE(invalid) +{ + json object{ + { "not-uint", true } + }; + + try { + util::json::require_uint(object, "/not-uint"_json_pointer); + BOOST_FAIL("exception expected"); + } catch (const util::json::property_error& ex) { + BOOST_REQUIRE_EQUAL(ex.type(), json::value_t::boolean); + BOOST_REQUIRE_EQUAL(ex.expected(), json::value_t::number_unsigned); + } +} + +BOOST_AUTO_TEST_SUITE_END()