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:
parent
56ab529752
commit
13b18a182a
Notes:
sideshowbarker
2024-07-17 07:16:27 +09:00
Author: https://github.com/trflynn89 Commit: https://github.com/SerenityOS/serenity/commit/13b18a182a Pull-request: https://github.com/SerenityOS/serenity/pull/16099 Reviewed-by: https://github.com/linusg
3 changed files with 129 additions and 0 deletions
|
@ -7,6 +7,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <AK/Concepts.h>
|
#include <AK/Concepts.h>
|
||||||
|
#include <AK/Error.h>
|
||||||
#include <AK/JsonArraySerializer.h>
|
#include <AK/JsonArraySerializer.h>
|
||||||
#include <AK/JsonValue.h>
|
#include <AK/JsonValue.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
|
@ -14,6 +15,9 @@
|
||||||
namespace AK {
|
namespace AK {
|
||||||
|
|
||||||
class JsonArray {
|
class JsonArray {
|
||||||
|
template<typename Callback>
|
||||||
|
using CallbackErrorType = decltype(declval<Callback>()(declval<JsonValue const&>()).release_error());
|
||||||
|
|
||||||
public:
|
public:
|
||||||
JsonArray() = default;
|
JsonArray() = default;
|
||||||
~JsonArray() = default;
|
~JsonArray() = default;
|
||||||
|
@ -76,6 +80,14 @@ public:
|
||||||
callback(value);
|
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; }
|
[[nodiscard]] Vector<JsonValue> const& values() const { return m_values; }
|
||||||
|
|
||||||
void ensure_capacity(size_t capacity) { m_values.ensure_capacity(capacity); }
|
void ensure_capacity(size_t capacity) { m_values.ensure_capacity(capacity); }
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Concepts.h>
|
||||||
|
#include <AK/Error.h>
|
||||||
#include <AK/HashMap.h>
|
#include <AK/HashMap.h>
|
||||||
#include <AK/JsonArray.h>
|
#include <AK/JsonArray.h>
|
||||||
#include <AK/JsonObjectSerializer.h>
|
#include <AK/JsonObjectSerializer.h>
|
||||||
|
@ -16,6 +18,9 @@
|
||||||
namespace AK {
|
namespace AK {
|
||||||
|
|
||||||
class JsonObject {
|
class JsonObject {
|
||||||
|
template<typename Callback>
|
||||||
|
using CallbackErrorType = decltype(declval<Callback>()(declval<String const&>(), declval<JsonValue const&>()).release_error());
|
||||||
|
|
||||||
public:
|
public:
|
||||||
JsonObject() = default;
|
JsonObject() = default;
|
||||||
~JsonObject() = default;
|
~JsonObject() = default;
|
||||||
|
@ -142,6 +147,14 @@ public:
|
||||||
callback(member.key, member.value);
|
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)
|
bool remove(StringView key)
|
||||||
{
|
{
|
||||||
return m_members.remove(key);
|
return m_members.remove(key);
|
||||||
|
|
|
@ -267,3 +267,107 @@ TEST_CASE(json_parse_fails_on_invalid_number)
|
||||||
|
|
||||||
#undef EXPECT_JSON_PARSE_TO_FAIL
|
#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>));
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue