Skip to content

Commit ca2cfed

Browse files
committed
types: Add Custom{Build,Read,Pass}Message hooks
Add CustomBuildMessage, CustomReadMessage, and CustomPassMessage hook functions. These functions can be defined to use custom code to convert C++ objects to and from Cap'n Proto messages. They work similarly to existing CustomBuildField, CustomReadField, and CustomPassField hooks, except they can be defined as normal functions not template functions, so they should be easier to use and require less verbosity in most cases, although they are less flexible. The unit tests added here are new but the feature was originally implemented in bitcoin/bitcoin#10102 but and is being ported because it's a general purpose feature.
1 parent 6aca5f3 commit ca2cfed

File tree

5 files changed

+146
-0
lines changed

5 files changed

+146
-0
lines changed

include/mp/proxy-types.h

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,19 @@ decltype(auto) CustomReadField(TypeList<LocalType> param,
662662
return read_dest.update([&](auto& value) { ReadOne<0>(param, invoke_context, input, value); });
663663
}
664664

665+
//! Overload CustomReadField to serialize objects that have CustomReadMessage
666+
//! overloads. Defining a CustomReadMessage overload is simpler than defining a
667+
//! CustomReadField overload because it only requires defining a normal
668+
//! function, not a template function, but less flexible.
669+
template <typename LocalType, typename Reader, typename ReadDest>
670+
decltype(auto) CustomReadField(TypeList<LocalType>, Priority<2>, InvokeContext& invoke_context, Reader&& reader,
671+
ReadDest&& read_dest,
672+
decltype(CustomReadMessage(invoke_context, reader.get(),
673+
std::declval<LocalType&>()))* enable = nullptr)
674+
{
675+
return read_dest.update([&](auto& value) { if (reader.has()) CustomReadMessage(invoke_context, reader.get(), value); });
676+
}
677+
665678
template <typename... LocalTypes, typename... Args>
666679
decltype(auto) ReadField(TypeList<LocalTypes...>, Args&&... args)
667680
{
@@ -719,6 +732,17 @@ bool CustomHasValue(InvokeContext& invoke_context, Values&&... value)
719732
return true;
720733
}
721734

735+
//! Overload CustomBuildField to serialize objects that have CustomBuildMessage
736+
//! overloads. Defining a CustomBuildMessage overload is simpler than defining a
737+
//! CustomBuildField overload because it only requires defining a normal
738+
//! function, not a template function, but less flexible.
739+
template <typename LocalType, typename Value, typename Output>
740+
void CustomBuildField(TypeList<LocalType>, Priority<2>, InvokeContext& invoke_context, Value&& value, Output&& output,
741+
decltype(CustomBuildMessage(invoke_context, value, std::move(output.get())))* enable = nullptr)
742+
{
743+
CustomBuildMessage(invoke_context, value, std::move(output.init()));
744+
}
745+
722746
template <typename... LocalTypes, typename Context, typename... Values, typename Output>
723747
void BuildField(TypeList<LocalTypes...>, Context& context, Output&& output, Values&&... values)
724748
{
@@ -1389,10 +1413,61 @@ struct ServerExcept : Parent
13891413
}
13901414
};
13911415

1416+
//! Helper for CustomPassField below. Call Accessor::get method if it has one,
1417+
//! otherwise return capnp::Void.
1418+
template <typename Accessor, typename Message>
1419+
decltype(auto) MaybeGet(Message&& message, decltype(Accessor::get(message))* enable = nullptr)
1420+
{
1421+
return Accessor::get(message);
1422+
}
1423+
1424+
template <typename Accessor>
1425+
::capnp::Void MaybeGet(...)
1426+
{
1427+
return {};
1428+
}
1429+
1430+
//! Helper for CustomPassField below. Call Accessor::init method if it has one,
1431+
//! otherwise do nothing.
1432+
template <typename Accessor, typename Message>
1433+
decltype(auto) MaybeInit(Message&& message, decltype(Accessor::get(message))* enable = nullptr)
1434+
{
1435+
return Accessor::init(message);
1436+
}
1437+
1438+
template <typename Accessor>
1439+
::capnp::Void MaybeInit(...)
1440+
{
1441+
return {};
1442+
}
1443+
1444+
//! Overload CustomPassField to serialize objects that have CustomPassMessage
1445+
//! overloads. Defining a CustomPassMessage overload is simpler than defining a
1446+
//! CustomPassField overload because it only requires defining a normal
1447+
//! function, not a template function, but less flexible.
1448+
template <typename Accessor, typename... LocalTypes, typename ServerContext, typename Fn, typename... Args>
1449+
auto CustomPassField(TypeList<LocalTypes...>, ServerContext& server_context, Fn&& fn, Args&&... args)
1450+
-> decltype(CustomPassMessage(server_context, MaybeGet<Accessor>(server_context.call_context.getParams()),
1451+
MaybeGet<Accessor>(server_context.call_context.getResults()), nullptr))
1452+
{
1453+
CustomPassMessage(server_context, MaybeGet<Accessor>(server_context.call_context.getParams()),
1454+
MaybeInit<Accessor>(server_context.call_context.getResults()),
1455+
[&](LocalTypes... param) { fn.invoke(server_context, std::forward<Args>(args)..., param...); });
1456+
}
1457+
13921458
template <class Accessor>
13931459
void CustomPassField();
13941460

13951461
//! PassField override calling CustomPassField function, if it exists.
1462+
//! Defining a CustomPassField or CustomPassMessage overload is useful for
1463+
//! input/output parameters. If an overload is not defined these parameters will
1464+
//! just be deserialized on the server side with ReadField into a temporary
1465+
//! variable, then the server method will be called passing the temporary
1466+
//! variable as a parameter, then the temporary variable will be serialized and
1467+
//! sent back to the client with BuildField. But if a PassField or PassMessage
1468+
//! overload is defined, the overload is called with a callback to invoke and
1469+
//! pass parameters to the server side function, and run arbitrary code before
1470+
//! and after invoking the function.
13961471
template <typename Accessor, typename... Args>
13971472
auto PassField(Priority<2>, Args&&... args) -> decltype(CustomPassField<Accessor>(std::forward<Args>(args)...))
13981473
{

test/mp/test/foo-types.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,45 @@ decltype(auto) CustomReadField(TypeList<FooCustom>, Priority<1>, InvokeContext&
2828
}
2929

3030
} // namespace test
31+
32+
inline void CustomBuildMessage(InvokeContext& invoke_context,
33+
const test::FooMessage& src,
34+
test::messages::FooMessage::Builder&& builder)
35+
{
36+
builder.setMessage(src.message + " build");
37+
}
38+
39+
inline void CustomReadMessage(InvokeContext& invoke_context,
40+
const test::messages::FooMessage::Reader& reader,
41+
test::FooMessage& dest)
42+
{
43+
dest.message = std::string{reader.getMessage()} + " read";
44+
}
45+
46+
inline void CustomBuildMessage(InvokeContext& invoke_context,
47+
const test::FooMutable& src,
48+
test::messages::FooMutable::Builder&& builder)
49+
{
50+
builder.setMessage(src.message + " build");
51+
}
52+
53+
inline void CustomReadMessage(InvokeContext& invoke_context,
54+
const test::messages::FooMutable::Reader& reader,
55+
test::FooMutable& dest)
56+
{
57+
dest.message = std::string{reader.getMessage()} + " read";
58+
}
59+
60+
inline void CustomPassMessage(InvokeContext& invoke_context,
61+
const test::messages::FooMutable::Reader& reader,
62+
test::messages::FooMutable::Builder builder,
63+
std::function<void(test::FooMutable&)>&& fn)
64+
{
65+
test::FooMutable mut;
66+
mut.message = std::string{reader.getMessage()} + " pass";
67+
fn(mut);
68+
builder.setMessage(mut.message + " return");
69+
}
3170
} // namespace mp
3271

3372
#endif // MP_TEST_FOO_TYPES_H

test/mp/test/foo.capnp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ interface FooInterface $Proxy.wrap("mp::test::FooImplementation") {
2525
callbackExtended @10 (context :Proxy.Context, callback :ExtendedCallback, arg: Int32) -> (result :Int32);
2626
passCustom @11 (arg :FooCustom) -> (result :FooCustom);
2727
passEmpty @12 (arg :FooEmpty) -> (result :FooEmpty);
28+
passMessage @13 (arg :FooMessage) -> (result :FooMessage);
29+
passMutable @14 (arg :FooMutable) -> (arg :FooMutable);
2830
}
2931

3032
interface FooCallback $Proxy.wrap("mp::test::FooCallback") {
@@ -50,6 +52,14 @@ struct FooCustom $Proxy.wrap("mp::test::FooCustom") {
5052
struct FooEmpty $Proxy.wrap("mp::test::FooEmpty") {
5153
}
5254

55+
struct FooMessage {
56+
message @0 :Text;
57+
}
58+
59+
struct FooMutable {
60+
message @0 :Text;
61+
}
62+
5363
struct Pair(T1, T2) {
5464
first @0 :T1;
5565
second @1 :T2;

test/mp/test/foo.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ struct FooEmpty
3131
{
3232
};
3333

34+
struct FooMessage
35+
{
36+
std::string message;
37+
};
38+
39+
struct FooMutable
40+
{
41+
std::string message;
42+
};
43+
3444
class FooCallback
3545
{
3646
public:
@@ -60,6 +70,8 @@ class FooImplementation
6070
int callbackExtended(ExtendedCallback& callback, int arg) { return callback.callExtended(arg); }
6171
FooCustom passCustom(FooCustom foo) { return foo; }
6272
FooEmpty passEmpty(FooEmpty foo) { return foo; }
73+
FooMessage passMessage(FooMessage foo) { foo.message += " call"; return foo; }
74+
void passMutable(FooMutable& foo) { foo.message += " call"; }
6375
std::shared_ptr<FooCallback> m_callback;
6476
};
6577

test/mp/test/test.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,16 @@ KJ_TEST("Call FooInterface methods")
108108

109109
foo->passEmpty(FooEmpty{});
110110

111+
FooMessage message1;
112+
message1.message = "init";
113+
FooMessage message2{foo->passMessage(message1)};
114+
KJ_EXPECT(message2.message == "init build read call build read");
115+
116+
FooMutable mut;
117+
mut.message = "init";
118+
foo->passMutable(mut);
119+
KJ_EXPECT(mut.message == "init build pass call return read");
120+
111121
disconnect_client();
112122
thread.join();
113123

0 commit comments

Comments
 (0)