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/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(" 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 kMethod,
76 MethodType kExpected,
77 typename InvalidImpl = MethodImplementation<kMethod>>
InvalidMethod(uint32_t)78 constexpr auto InvalidMethod(uint32_t) {
79 if constexpr (kExpected == MethodType::kUnary) {
80 static_assert(
81 kCheckMethodSignature<decltype(kMethod)>,
82 _PW_RPC_FUNCTION_ERROR("unary", "Status", "Request, Response"));
83 } else if constexpr (kExpected == MethodType::kServerStreaming) {
84 static_assert(
85 kCheckMethodSignature<decltype(kMethod)>,
86 _PW_RPC_FUNCTION_ERROR(
87 "server streaming", "void", "Request, ServerWriter<Response>&"));
88 } else if constexpr (kExpected == MethodType::kClientStreaming) {
89 static_assert(
90 kCheckMethodSignature<decltype(kMethod)>,
91 _PW_RPC_FUNCTION_ERROR(
92 "client streaming", "void", "ServerReader<Request, Request>&"));
93 } else if constexpr (kExpected == MethodType::kBidirectionalStreaming) {
94 static_assert(
95 kCheckMethodSignature<decltype(kMethod)>,
96 _PW_RPC_FUNCTION_ERROR("bidirectional streaming",
97 "void",
98 "ServerReaderWriter<Request, Response>&"));
99 } else {
100 static_assert(kCheckMethodSignature<decltype(kMethod)>,
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 kMethod, typename MethodImpl, MethodType kType, typename... Args>
GetMethodFor(uint32_t id,Args &&...args)112 constexpr auto GetMethodFor(uint32_t id, Args&&... args) {
113 if constexpr (MethodTraits<decltype(kMethod)>::kType != kType) {
114 return InvalidMethod<kMethod, kType>(id);
115 } else if constexpr (kType == MethodType::kUnary) {
116 if constexpr (MethodTraits<decltype(kMethod)>::kSynchronous) {
117 return MethodImpl::template SynchronousUnary<kMethod>(
118 id, std::forward<Args>(args)...);
119 } else {
120 return MethodImpl::template AsynchronousUnary<kMethod>(
121 id, std::forward<Args>(args)...);
122 }
123 } else if constexpr (kType == MethodType::kServerStreaming) {
124 return MethodImpl::template ServerStreaming<kMethod>(
125 id, std::forward<Args>(args)...);
126 } else if constexpr (kType == MethodType::kClientStreaming) {
127 return MethodImpl::template ClientStreaming<kMethod>(
128 id, std::forward<Args>(args)...);
129 } else if constexpr (kType == MethodType::kBidirectionalStreaming) {
130 return MethodImpl::template BidirectionalStreaming<kMethod>(
131 id, std::forward<Args>(args)...);
132 } else {
133 static_assert(kCheckMethodSignature<MethodImpl>, "Invalid MethodType");
134 }
135 }
136
137 } // namespace pw::rpc::internal
138