1 // Copyright 2020 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15
16 #include <type_traits>
17 #include <utility>
18
19 #include "pw_rpc/internal/method.h"
20 #include "pw_rpc/internal/method_type.h"
21
22 namespace pw::rpc::internal {
23
24 // Base class for different combinations of possible service methods. Derived
25 // classes should contain a union of different method types, one of which is a
26 // base Method.
27 class MethodUnion {
28 public:
29 constexpr const Method& method() const;
30 };
31
32 class CoreMethodUnion : public MethodUnion {
33 public:
method()34 constexpr const Method& method() const { return impl_.method; }
35
36 private:
37 // All derived MethodUnions must contain a union of different method
38 // implementations as their only member.
39 union {
40 Method method;
41 } impl_;
42 };
43
method()44 constexpr const Method& MethodUnion::method() const {
45 // This is an ugly hack. As all MethodUnion classes contain a union of Method
46 // derivatives, CoreMethodUnion is used to extract a generic Method from the
47 // specific implementation.
48 return static_cast<const CoreMethodUnion*>(this)->method();
49 }
50
51 // Templated false value for use in static_assert(false) statements.
52 template <typename...>
53 constexpr std::false_type kCheckMethodSignature{};
54
55 // In static_assert messages, use newlines in GCC since it displays them
56 // correctly. Clang displays \n, which is not helpful.
57 #ifdef __clang__
58 #define _PW_RPC_FORMAT_ERROR_MESSAGE(msg, signature) msg " " signature
59 #else
60 #define _PW_RPC_FORMAT_ERROR_MESSAGE(msg, signature) \
61 "\n" msg "\n\n " signature "\n"
62 #endif // __clang__
63
64 #define _PW_RPC_FUNCTION_ERROR(type, return_type, args) \
65 _PW_RPC_FORMAT_ERROR_MESSAGE( \
66 "This RPC is a " type \
67 " RPC, but its function signature is not correct. The function " \
68 "signature is determined by the protobuf library in use, but " type \
69 " RPC implementations generally take the form:", \
70 return_type " MethodName(ServerContext&, " args ")")
71
72 // This function is called if an RPC method implementation's signature is not
73 // correct. It triggers a static_assert with an error message tailored to the
74 // expected RPC type.
75 template <auto method,
76 MethodType expected,
77 typename InvalidImpl = MethodImplementation<method>>
InvalidMethod(uint32_t)78 constexpr auto InvalidMethod(uint32_t) {
79 if constexpr (expected == MethodType::kUnary) {
80 static_assert(
81 kCheckMethodSignature<decltype(method)>,
82 _PW_RPC_FUNCTION_ERROR("unary", "Status", "Request, Response"));
83 } else if constexpr (expected == MethodType::kServerStreaming) {
84 static_assert(
85 kCheckMethodSignature<decltype(method)>,
86 _PW_RPC_FUNCTION_ERROR(
87 "server streaming", "void", "Request, ServerWriter<Response>&"));
88 } else if constexpr (expected == MethodType::kClientStreaming) {
89 static_assert(
90 kCheckMethodSignature<decltype(method)>,
91 _PW_RPC_FUNCTION_ERROR(
92 "client streaming", "Status", "ServerReader<Request>&, Response"));
93 } else if constexpr (expected == MethodType::kBidirectionalStreaming) {
94 static_assert(kCheckMethodSignature<decltype(method)>,
95 _PW_RPC_FUNCTION_ERROR(
96 "bidirectional streaming",
97 "void",
98 "ServerReader<Request>&, ServerWriter<Response>&"));
99 } else {
100 static_assert(kCheckMethodSignature<decltype(method)>,
101 "Unsupported MethodType");
102 }
103 return InvalidImpl::Invalid();
104 }
105
106 #undef _PW_RPC_FORMAT_ERROR_MESSAGE
107 #undef _PW_RPC_FUNCTION_ERROR
108
109 // This function checks the type of the method and calls the appropriate
110 // function to create the method instance.
111 template <auto method, typename MethodImpl, MethodType type, typename... Args>
GetMethodFor(uint32_t id,Args &&...args)112 constexpr auto GetMethodFor(uint32_t id, Args&&... args) {
113 if constexpr (MethodTraits<decltype(method)>::kType != type) {
114 return InvalidMethod<method, type>(id);
115 } else if constexpr (type == MethodType::kUnary) {
116 return MethodImpl::template Unary<method>(id, std::forward<Args>(args)...);
117 } else if constexpr (type == MethodType::kServerStreaming) {
118 return MethodImpl::template ServerStreaming<method>(
119 id, std::forward<Args>(args)...);
120 } else if constexpr (type == MethodType::kClientStreaming) {
121 return MethodImpl::template ClientStreaming<method>(
122 id, std::forward<Args>(args)...);
123 } else if constexpr (type == MethodType::kBidirectionalStreaming) {
124 return MethodImpl::template BidirectionalStreaming<method>(
125 id, std::forward<Args>(args)...);
126 } else {
127 static_assert(kCheckMethodSignature<MethodImpl>, "Invalid MethodType");
128 }
129 }
130
131 } // namespace pw::rpc::internal
132