Action Engine
Loading...
Searching...
No Matches
status_macros.h
1/*
2 * Copyright 2025 Google LLC
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#ifndef ACTIONENGINE_UTIL_STATUS_MACROS_H_
18#define ACTIONENGINE_UTIL_STATUS_MACROS_H_
19
20#include "status_builder.h"
21
22// Evaluates an expression that produces a `absl::Status`. If the status
23// is not ok, returns it from the current function.
24//
25// For example:
26// absl::Status MultiStepFunction() {
27// RETURN_IF_ERROR(Function(args...));
28// RETURN_IF_ERROR(foo.Method(args...));
29// return absl::OkStatus();
30// }
31//
32// The macro ends with a `act::util::StatusBuilder` which allows the returned
33// status to be extended with more details. Any chained expressions after the
34// macro will not be evaluated unless there is an error.
35//
36// For example:
37// absl::Status MultiStepFunction() {
38// RETURN_IF_ERROR(Function(args...)) << "in MultiStepFunction";
39// RETURN_IF_ERROR(foo.Method(args...)).Log(base_logging::ERROR)
40// << "while processing query: " << query.DebugString();
41// return absl::OkStatus();
42// }
43//
44// `act::util::StatusBuilder` supports adapting the builder chain using a
45// `With` method and a functor. This allows for powerful extensions to the
46// macro.
47//
48// For example, teams can define local policies to use across their code:
49//
50// StatusBuilder TeamPolicy(StatusBuilder builder) {
51// return std::move(builder.Log(base_logging::WARNING).Attach(...));
52// }
53//
54// RETURN_IF_ERROR(foo()).With(TeamPolicy);
55// RETURN_IF_ERROR(bar()).With(TeamPolicy);
56//
57// Changing the return type allows the macro to be used with Task and Rpc
58// interfaces. See `act::util::TaskReturn` and `rpc::RpcSetStatus` for
59// details.
60//
61// void Read(StringPiece name, act::util::Task* task) {
62// int64 id;
63// RETURN_IF_ERROR(GetIdForName(name, &id)).With(TaskReturn(task));
64// RETURN_IF_ERROR(ReadForId(id)).With(TaskReturn(task));
65// task->Return();
66// }
67//
68// If using this macro inside a lambda, you need to annotate the return type
69// to avoid confusion between a `act::util::StatusBuilder` and a
70// `absl::Status` type. E.g.
71//
72// []() -> absl::Status {
73// RETURN_IF_ERROR(Function(args...));
74// RETURN_IF_ERROR(foo.Method(args...));
75// return absl::OkStatus();
76// }
77#define RETURN_IF_ERROR(expr) \
78 STATUS_MACROS_IMPL_ELSE_BLOCKER_ \
79 if (act::util::status_macro_internal::StatusAdaptorForMacros \
80 status_macro_internal_adaptor = {(expr), ACTIONENGINE_LOC}) { \
81 } else /* NOLINT */ \
82 return status_macro_internal_adaptor.Consume()
83
84// Executes an expression `rexpr` that returns a `absl::StatusOr<T>`. On
85// OK, extracts its value into the variable defined by `lhs`, otherwise returns
86// from the current function. By default, the error status is returned
87// unchanged, but it may be modified by an `error_expression`. If there is an
88// error, `lhs` is not evaluated; thus any side effects that `lhs` may have
89// only occurred in the success case.
90//
91// Interface:
92//
93// ASSIGN_OR_RETURN(lhs, rexpr)
94// ASSIGN_OR_RETURN(lhs, rexpr, error_expression);
95//
96// WARNING: expands into multiple statements; it cannot be used in a single
97// statement (e.g. as the body of an if statement without {})!
98//
99// Example: Declaring and initializing a new variable (ValueType can be anything
100// that can be initialized with assignment, including references):
101// ASSIGN_OR_RETURN(ValueType value, MaybeGetValue(arg));
102//
103// Example: Assigning to an existing variable:
104// ValueType value;
105// ASSIGN_OR_RETURN(value, MaybeGetValue(arg));
106//
107// Example: Assigning to an expression with side effects:
108// MyProto data;
109// ASSIGN_OR_RETURN(*data.mutable_str(), MaybeGetValue(arg));
110// // No field "str" is added on error.
111//
112// Example: Assigning to a std::unique_ptr.
113// ASSIGN_OR_RETURN(std::unique_ptr<T> ptr, MaybeGetPtr(arg));
114//
115// If passed, the `error_expression` is evaluated to produce the return
116// value. The expression may reference any variable visible in scope, as
117// well as a `act::util::StatusBuilder` object populated with the error and
118// named by a single underscore `_`. The expression typically uses the
119// builder to modify the status and is returned directly in manner similar
120// to RETURN_IF_ERROR. The expression may, however, evaluate to any type
121// returnable by the function, including (void). For example:
122//
123// Example: Adjusting the error message.
124// ASSIGN_OR_RETURN(ValueType value, MaybeGetValue(query),
125// _ << "while processing query " << query.DebugString());
126//
127// Example: Logging the error on failure.
128// ASSIGN_OR_RETURN(ValueType value, MaybeGetValue(query), _.LogError());
129//
130#define ASSIGN_OR_RETURN(...) \
131 STATUS_MACROS_IMPL_GET_VARIADIC_((__VA_ARGS__, \
132 STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_3_, \
133 STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_2_)) \
134 (__VA_ARGS__)
135
136// =================================================================
137// == Implementation details, do not rely on anything below here. ==
138// =================================================================
139
140// MSVC incorrectly expands variadic macros, splice together a macro call to
141// work around the bug.
142#define STATUS_MACROS_IMPL_GET_VARIADIC_HELPER_(_1, _2, _3, NAME, ...) NAME
143#define STATUS_MACROS_IMPL_GET_VARIADIC_(args) \
144 STATUS_MACROS_IMPL_GET_VARIADIC_HELPER_ args
145
146#define STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_2_(lhs, rexpr) \
147 STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_( \
148 STATUS_MACROS_IMPL_CONCAT_(_status_or_value, __LINE__), lhs, rexpr, \
149 return act::util::StatusBuilder( \
150 std::move(STATUS_MACROS_IMPL_CONCAT_(_status_or_value, __LINE__)) \
151 .status(), \
152 ACTIONENGINE_LOC))
153#define STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_3_(lhs, rexpr, error_expression) \
154 STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_( \
155 STATUS_MACROS_IMPL_CONCAT_(_status_or_value, __LINE__), lhs, rexpr, \
156 act::util::StatusBuilder _( \
157 std::move(STATUS_MACROS_IMPL_CONCAT_(_status_or_value, __LINE__)) \
158 .status(), \
159 ACTIONENGINE_LOC); \
160 (void)_; /* error_expression is allowed to not use this variable */ \
161 return (error_expression))
162#define STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_(statusor, lhs, rexpr, \
163 error_expression) \
164 auto statusor = (rexpr); \
165 if (ABSL_PREDICT_FALSE(!statusor.ok())) { \
166 error_expression; \
167 } \
168 lhs = std::move(statusor).value()
169
170// Internal helper for concatenating macro values.
171#define STATUS_MACROS_IMPL_CONCAT_INNER_(x, y) x##y
172#define STATUS_MACROS_IMPL_CONCAT_(x, y) STATUS_MACROS_IMPL_CONCAT_INNER_(x, y)
173
174// The GNU compiler emits a warning for code like:
175//
176// if (foo)
177// if (bar) { } else baz;
178//
179// because it thinks you might want the else to bind to the first if. This
180// leads to problems with code like:
181//
182// if (do_expr) RETURN_IF_ERROR(expr) << "Some message";
183//
184// The "switch (0) case 0:" idiom is used to suppress this.
185#define STATUS_MACROS_IMPL_ELSE_BLOCKER_ \
186 switch (0) \
187 case 0: \
188 default: // NOLINT
189
190namespace act::util {
191namespace status_macro_internal {
192
193// Provides a conversion to bool so that it can be used inside an if statement
194// that declares a variable.
195class StatusAdaptorForMacros {
196 public:
197 StatusAdaptorForMacros(const absl::Status& status, source_location location)
198 : builder_(status, location) {}
199
200 StatusAdaptorForMacros(absl::Status&& status, source_location location)
201 : builder_(std::move(status), location) {}
202
203 StatusAdaptorForMacros(const StatusBuilder& builder,
204 source_location /*location*/)
205 : builder_(builder) {}
206
207 StatusAdaptorForMacros(StatusBuilder&& builder, source_location /*location*/)
208 : builder_(std::move(builder)) {}
209
210 StatusAdaptorForMacros(const StatusAdaptorForMacros&) = delete;
211 StatusAdaptorForMacros& operator=(const StatusAdaptorForMacros&) = delete;
212
213 explicit operator bool() const { return ABSL_PREDICT_TRUE(builder_.ok()); }
214
215 StatusBuilder&& Consume() { return std::move(builder_); }
216
217 private:
218 StatusBuilder builder_;
219};
220
221} // namespace status_macro_internal
222} // namespace act::util
223
224#endif // ACTIONENGINE_UTIL_STATUS_MACROS_H_