1 // Copyright 2022 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 "pw_assert/assert.h"
17 #include "pw_status/status.h"
18
19 namespace pw {
20 namespace rpc {
21
22 // A `SynchronousCallResult<Response>` is an object that contains the result of
23 // a `SynchronousCall`. When synchronous calls are made, errors could occur for
24 // multiple reasons. There could have been an error at either the RPC layer or
25 // Server; or the client-specified timeout might have occurred. A user can query
26 // this object to determine what type of error occurred, so that they can handle
27 // it appropriately. If the server responded, the response() and dereference
28 // operators will provide access to the Response.
29 //
30 // Example:
31 //
32 // SynchronousCallResult<MyResponse> result =
33 // SynchronousCallFor<MyService::Method>(client, request, timeout);
34 // if (result.is_rpc_error()) {
35 // ShutdownClient(client);
36 // } else if (result.is_timeout()) {
37 // RetryCall(client, request);
38 // } else if (result.is_server_error()) {
39 // return result.status();
40 // }
41 // PW_ASSERT(result.ok());
42 // return std::move(result).response();
43 //
44 // For some RPCs, the server could have responded with a non-Ok Status but with
45 // a valid Response object. For example, if the server was ran out of space in a
46 // buffer, it might return a Status of ResourceExhausted, but the response
47 // contains as much data as could fit. In this situation, users should be
48 // careful not to treat the error as fatal.
49 //
50 // Example:
51 //
52 // SynchronousCallResult<BufferResponse> result =
53 // SynchronousCall<MyService::Read>(client, request);
54 // if (result.is_rpc_error()) {
55 // ShutdownClient(client);
56 // }
57 // PW_ASSERT(result.is_server_response());
58 // HandleServerResponse(result.status(), result.response());
59 //
60
61 namespace internal {
62 enum class SynchronousCallStatus {
63 kInvalid,
64 kTimeout,
65 kRpc,
66 kServer,
67 };
68 } // namespace internal
69
70 template <typename Response>
71 class SynchronousCallResult {
72 public:
73 // Error Constructors
74 constexpr static SynchronousCallResult Timeout();
75 constexpr static SynchronousCallResult RpcError(Status status);
76
77 // Server Response Constructor
SynchronousCallResult(Status status,Response response)78 constexpr explicit SynchronousCallResult(Status status, Response response)
79 : call_status_(internal::SynchronousCallStatus::kServer),
80 status_(status),
81 response_(std::move(response)) {}
82
83 constexpr SynchronousCallResult() = default;
84 ~SynchronousCallResult() = default;
85
86 // Copyable if `Response` is copyable.
87 constexpr SynchronousCallResult(const SynchronousCallResult&) = default;
88 constexpr SynchronousCallResult& operator=(const SynchronousCallResult&) =
89 default;
90
91 // Movable if `Response` is movable.
92 constexpr SynchronousCallResult(SynchronousCallResult&&) = default;
93 constexpr SynchronousCallResult& operator=(SynchronousCallResult&&) = default;
94
95 // Returns true if there was a timeout, an rpc error or the server returned a
96 // non-Ok status.
97 [[nodiscard]] constexpr bool is_error() const;
98
99 // Returns true if the server returned a response with an Ok status.
100 [[nodiscard]] constexpr bool ok() const;
101
102 // Returns true if the server responded with a non-Ok status.
103 [[nodiscard]] constexpr bool is_server_error() const;
104
105 [[nodiscard]] constexpr bool is_timeout() const;
106 [[nodiscard]] constexpr bool is_rpc_error() const;
107 [[nodiscard]] constexpr bool is_server_response() const;
108
109 [[nodiscard]] constexpr Status status() const;
110
111 // SynchronousCallResult<Response>::response()
112 // SynchronousCallResult<Response>::operator*()
113 // SynchronousCallResult<Response>::operator->()
114 //
115 // Accessors to the held value if `this->is_server_response()`. Otherwise,
116 // terminates the process.
117 constexpr const Response& response() const&;
118 constexpr Response& response() &;
119 constexpr const Response&& response() const&&;
120 constexpr Response&& response() &&;
121
122 constexpr const Response& operator*() const&;
123 constexpr Response& operator*() &;
124 constexpr const Response&& operator*() const&&;
125 constexpr Response&& operator*() &&;
126
127 constexpr const Response* operator->() const;
128 constexpr Response* operator->();
129
130 private:
131 // This constructor is private to protect against invariants that might occur
132 // when constructing with a SynchronousCallStatus.
SynchronousCallResult(internal::SynchronousCallStatus call_status,Status status)133 constexpr explicit SynchronousCallResult(
134 internal::SynchronousCallStatus call_status, Status status)
135 : call_status_(call_status), status_(status) {}
136
137 internal::SynchronousCallStatus call_status_ =
138 internal::SynchronousCallStatus::kInvalid;
139 Status status_{};
140 Response response_{};
141 };
142
143 // Implementations
144
145 template <typename Response>
146 constexpr SynchronousCallResult<Response>
Timeout()147 SynchronousCallResult<Response>::Timeout() {
148 return SynchronousCallResult(internal::SynchronousCallStatus::kTimeout,
149 Status::DeadlineExceeded());
150 }
151
152 template <typename Response>
153 constexpr SynchronousCallResult<Response>
RpcError(Status status)154 SynchronousCallResult<Response>::RpcError(Status status) {
155 return SynchronousCallResult(internal::SynchronousCallStatus::kRpc, status);
156 }
157
158 template <typename Response>
is_error()159 constexpr bool SynchronousCallResult<Response>::is_error() const {
160 return !ok();
161 }
162
163 template <typename Response>
ok()164 constexpr bool SynchronousCallResult<Response>::ok() const {
165 return is_server_response() && status_.ok();
166 }
167
168 template <typename Response>
is_server_error()169 constexpr bool SynchronousCallResult<Response>::is_server_error() const {
170 return is_server_response() && !status_.ok();
171 }
172
173 template <typename Response>
is_timeout()174 constexpr bool SynchronousCallResult<Response>::is_timeout() const {
175 return call_status_ == internal::SynchronousCallStatus::kTimeout;
176 }
177
178 template <typename Response>
is_rpc_error()179 constexpr bool SynchronousCallResult<Response>::is_rpc_error() const {
180 return call_status_ == internal::SynchronousCallStatus::kRpc;
181 }
182
183 template <typename Response>
is_server_response()184 constexpr bool SynchronousCallResult<Response>::is_server_response() const {
185 return call_status_ == internal::SynchronousCallStatus::kServer;
186 }
187
188 template <typename Response>
status()189 constexpr Status SynchronousCallResult<Response>::status() const {
190 PW_ASSERT(call_status_ != internal::SynchronousCallStatus::kInvalid);
191 return status_;
192 }
193
194 template <typename Response>
response()195 constexpr const Response& SynchronousCallResult<Response>::response() const& {
196 PW_ASSERT(is_server_response());
197 return response_;
198 }
199
200 template <typename Response>
response()201 constexpr Response& SynchronousCallResult<Response>::response() & {
202 PW_ASSERT(is_server_response());
203 return response_;
204 }
205
206 template <typename Response>
response()207 constexpr const Response&& SynchronousCallResult<Response>::response() const&& {
208 PW_ASSERT(is_server_response());
209 return std::move(response_);
210 }
211
212 template <typename Response>
response()213 constexpr Response&& SynchronousCallResult<Response>::response() && {
214 PW_ASSERT(is_server_response());
215 return std::move(response_);
216 }
217
218 template <typename Response>
219 constexpr const Response& SynchronousCallResult<Response>::operator*() const& {
220 PW_ASSERT(is_server_response());
221 return response_;
222 }
223
224 template <typename Response>
225 constexpr Response& SynchronousCallResult<Response>::operator*() & {
226 PW_ASSERT(is_server_response());
227 return response_;
228 }
229
230 template <typename Response>
231 constexpr const Response&& SynchronousCallResult<Response>::operator*()
232 const&& {
233 PW_ASSERT(is_server_response());
234 return std::move(response_);
235 }
236
237 template <typename Response>
238 constexpr Response&& SynchronousCallResult<Response>::operator*() && {
239 PW_ASSERT(is_server_response());
240 return std::move(response_);
241 }
242
243 template <typename Response>
244 constexpr const Response* SynchronousCallResult<Response>::operator->() const {
245 PW_ASSERT(is_server_response());
246 return &response_;
247 }
248
249 template <typename Response>
250 constexpr Response* SynchronousCallResult<Response>::operator->() {
251 PW_ASSERT(is_server_response());
252 return &response_;
253 }
254
255 } // namespace rpc
256
257 // Conversion functions for usage with PW_TRY and PW_TRY_ASSIGN.
258 namespace internal {
259
260 template <typename T>
ConvertToStatus(const rpc::SynchronousCallResult<T> & result)261 constexpr Status ConvertToStatus(const rpc::SynchronousCallResult<T>& result) {
262 return result.status();
263 }
264
265 template <typename T>
ConvertToValue(rpc::SynchronousCallResult<T> & result)266 constexpr T ConvertToValue(rpc::SynchronousCallResult<T>& result) {
267 return std::move(result.response());
268 }
269
270 } // namespace internal
271 } // namespace pw
272