From 13b18a182a13ecbfc703e4cef9175444b5519fcb Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Thu, 17 Nov 2022 08:15:01 -0500 Subject: [PATCH] AK: Add JSON object/array for-each methods for fallible callbacks This allows the provided callback to return an ErrorOr-like type to propagate errors back to the caller. --- AK/JsonArray.h | 12 +++++ AK/JsonObject.h | 13 ++++++ Tests/AK/TestJSON.cpp | 104 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+) diff --git a/AK/JsonArray.h b/AK/JsonArray.h index 0a2de05246..932f8b77ac 100644 --- a/AK/JsonArray.h +++ b/AK/JsonArray.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include #include @@ -14,6 +15,9 @@ namespace AK { class JsonArray { + template + using CallbackErrorType = decltype(declval()(declval()).release_error()); + public: JsonArray() = default; ~JsonArray() = default; @@ -76,6 +80,14 @@ public: callback(value); } + template Callback> + ErrorOr> try_for_each(Callback&& callback) const + { + for (auto const& value : m_values) + TRY(callback(value)); + return {}; + } + [[nodiscard]] Vector const& values() const { return m_values; } void ensure_capacity(size_t capacity) { m_values.ensure_capacity(capacity); } diff --git a/AK/JsonObject.h b/AK/JsonObject.h index a15e3ec665..b59000c93c 100644 --- a/AK/JsonObject.h +++ b/AK/JsonObject.h @@ -7,6 +7,8 @@ #pragma once +#include +#include #include #include #include @@ -16,6 +18,9 @@ namespace AK { class JsonObject { + template + using CallbackErrorType = decltype(declval()(declval(), declval()).release_error()); + public: JsonObject() = default; ~JsonObject() = default; @@ -142,6 +147,14 @@ public: callback(member.key, member.value); } + template Callback> + ErrorOr> try_for_each_member(Callback&& callback) const + { + for (auto const& member : m_members) + TRY(callback(member.key, member.value)); + return {}; + } + bool remove(StringView key) { return m_members.remove(key); diff --git a/Tests/AK/TestJSON.cpp b/Tests/AK/TestJSON.cpp index 7328d69e24..f748dc530b 100644 --- a/Tests/AK/TestJSON.cpp +++ b/Tests/AK/TestJSON.cpp @@ -267,3 +267,107 @@ TEST_CASE(json_parse_fails_on_invalid_number) #undef EXPECT_JSON_PARSE_TO_FAIL } + +struct CustomError { +}; + +template +class CustomErrorOr { +public: + CustomErrorOr(T) + : m_is_error(false) + { + } + + CustomErrorOr(CustomError) + : m_is_error(true) + { + } + + bool is_error() const { return m_is_error; } + CustomError release_error() { return CustomError {}; } + T release_value() { return T {}; } + +private: + bool m_is_error { false }; +}; + +TEST_CASE(fallible_json_object_for_each) +{ + String raw_json = R"( + { + "name": "anon", + "home": "/home/anon", + "default_browser": "Ladybird" + })"; + + auto json = JsonValue::from_string(raw_json).value(); + auto const& object = json.as_object(); + + MUST(object.try_for_each_member([](auto const&, auto const&) -> ErrorOr { + return {}; + })); + + auto result1 = object.try_for_each_member([](auto const&, auto const&) -> ErrorOr { + return Error::from_string_view("nanananana"sv); + }); + EXPECT(result1.is_error()); + EXPECT_EQ(result1.error().string_literal(), "nanananana"sv); + + auto result2 = object.try_for_each_member([](auto const&, auto const&) -> ErrorOr { + return CustomError {}; + }); + EXPECT(result2.is_error()); + EXPECT((IsSame)); + + auto result3 = object.try_for_each_member([](auto const&, auto const&) -> CustomErrorOr { + return 42; + }); + EXPECT(!result3.is_error()); + + auto result4 = object.try_for_each_member([](auto const&, auto const&) -> CustomErrorOr { + return CustomError {}; + }); + EXPECT(result4.is_error()); + EXPECT((IsSame)); +} + +TEST_CASE(fallible_json_array_for_each) +{ + String raw_json = R"( + [ + "anon", + "/home/anon", + "Ladybird" + ])"; + + auto json = JsonValue::from_string(raw_json).value(); + auto const& array = json.as_array(); + + MUST(array.try_for_each([](auto const&) -> ErrorOr { + return {}; + })); + + auto result1 = array.try_for_each([](auto const&) -> ErrorOr { + return Error::from_string_view("nanananana"sv); + }); + EXPECT(result1.is_error()); + EXPECT_EQ(result1.error().string_literal(), "nanananana"sv); + + auto result2 = array.try_for_each([](auto const&) -> ErrorOr { + return CustomError {}; + }); + EXPECT(result2.is_error()); + EXPECT((IsSame)); + + auto result3 = array.try_for_each([](auto const&) -> CustomErrorOr { + return 42; + }); + EXPECT(!result3.is_error()); + + auto result4 = array.try_for_each([](auto const&) -> CustomErrorOr { + return CustomError {}; + }); + EXPECT(result4.is_error()); + EXPECT((IsSame)); +}