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.
This commit is contained in:
Timothy Flynn 2022-11-17 08:15:01 -05:00 committed by Linus Groh
parent 56ab529752
commit 13b18a182a
3 changed files with 129 additions and 0 deletions

View file

@ -7,6 +7,7 @@
#pragma once
#include <AK/Concepts.h>
#include <AK/Error.h>
#include <AK/JsonArraySerializer.h>
#include <AK/JsonValue.h>
#include <AK/Vector.h>
@ -14,6 +15,9 @@
namespace AK {
class JsonArray {
template<typename Callback>
using CallbackErrorType = decltype(declval<Callback>()(declval<JsonValue const&>()).release_error());
public:
JsonArray() = default;
~JsonArray() = default;
@ -76,6 +80,14 @@ public:
callback(value);
}
template<FallibleFunction<JsonValue const&> Callback>
ErrorOr<void, CallbackErrorType<Callback>> try_for_each(Callback&& callback) const
{
for (auto const& value : m_values)
TRY(callback(value));
return {};
}
[[nodiscard]] Vector<JsonValue> const& values() const { return m_values; }
void ensure_capacity(size_t capacity) { m_values.ensure_capacity(capacity); }

View file

@ -7,6 +7,8 @@
#pragma once
#include <AK/Concepts.h>
#include <AK/Error.h>
#include <AK/HashMap.h>
#include <AK/JsonArray.h>
#include <AK/JsonObjectSerializer.h>
@ -16,6 +18,9 @@
namespace AK {
class JsonObject {
template<typename Callback>
using CallbackErrorType = decltype(declval<Callback>()(declval<String const&>(), declval<JsonValue const&>()).release_error());
public:
JsonObject() = default;
~JsonObject() = default;
@ -142,6 +147,14 @@ public:
callback(member.key, member.value);
}
template<FallibleFunction<String const&, JsonValue const&> Callback>
ErrorOr<void, CallbackErrorType<Callback>> 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);

View file

@ -267,3 +267,107 @@ TEST_CASE(json_parse_fails_on_invalid_number)
#undef EXPECT_JSON_PARSE_TO_FAIL
}
struct CustomError {
};
template<typename T>
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<void> {
return {};
}));
auto result1 = object.try_for_each_member([](auto const&, auto const&) -> ErrorOr<void> {
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<void, CustomError> {
return CustomError {};
});
EXPECT(result2.is_error());
EXPECT((IsSame<decltype(result2.release_error()), CustomError>));
auto result3 = object.try_for_each_member([](auto const&, auto const&) -> CustomErrorOr<int> {
return 42;
});
EXPECT(!result3.is_error());
auto result4 = object.try_for_each_member([](auto const&, auto const&) -> CustomErrorOr<int> {
return CustomError {};
});
EXPECT(result4.is_error());
EXPECT((IsSame<decltype(result4.release_error()), CustomError>));
}
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<void> {
return {};
}));
auto result1 = array.try_for_each([](auto const&) -> ErrorOr<void> {
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<void, CustomError> {
return CustomError {};
});
EXPECT(result2.is_error());
EXPECT((IsSame<decltype(result2.release_error()), CustomError>));
auto result3 = array.try_for_each([](auto const&) -> CustomErrorOr<int> {
return 42;
});
EXPECT(!result3.is_error());
auto result4 = array.try_for_each([](auto const&) -> CustomErrorOr<int> {
return CustomError {};
});
EXPECT(result4.is_error());
EXPECT((IsSame<decltype(result4.release_error()), CustomError>));
}