// Copyright 2021 gRPC authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef GRPC_SRC_CORE_LIB_PROMISE_TRY_SEQ_H #define GRPC_SRC_CORE_LIB_PROMISE_TRY_SEQ_H #include #include #include #include "absl/log/check.h" #include "absl/meta/type_traits.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include #include "src/core/lib/promise/detail/basic_seq.h" #include "src/core/lib/promise/detail/promise_like.h" #include "src/core/lib/promise/detail/seq_state.h" #include "src/core/lib/promise/detail/status.h" #include "src/core/lib/promise/poll.h" #include "src/core/lib/promise/status_flag.h" namespace grpc_core { namespace promise_detail { template struct TrySeqTraitsWithSfinae { using UnwrappedType = T; using WrappedType = absl::StatusOr; template static auto CallFactory(Next* next, T&& value) { return next->Make(std::forward(value)); } static bool IsOk(const T&) { return true; } static const char* ErrorString(const T&) { abort(); } template static R ReturnValue(T&&) { abort(); } template static auto CallSeqFactory(F& f, Elem&& elem, T&& value) -> decltype(f(std::forward(elem), std::forward(value))) { return f(std::forward(elem), std::forward(value)); } template static Poll CheckResultAndRunNext(T prior, RunNext run_next) { return run_next(std::move(prior)); } }; template struct TrySeqTraitsWithSfinae> { using UnwrappedType = T; using WrappedType = absl::StatusOr; template static auto CallFactory(Next* next, absl::StatusOr&& status) { return next->Make(std::move(*status)); } static bool IsOk(const absl::StatusOr& status) { return status.ok(); } static std::string ErrorString(const absl::StatusOr& status) { return status.status().ToString(); } template static R ReturnValue(absl::StatusOr&& status) { return FailureStatusCast(status.status()); } template static auto CallSeqFactory(F& f, Elem&& elem, absl::StatusOr value) -> decltype(f(std::forward(elem), std::move(*value))) { return f(std::forward(elem), std::move(*value)); } template static Poll CheckResultAndRunNext(absl::StatusOr prior, RunNext run_next) { if (!prior.ok()) return FailureStatusCast(prior.status()); return run_next(std::move(prior)); } }; template struct AllowGenericTrySeqTraits { static constexpr bool value = true; }; template <> struct AllowGenericTrySeqTraits { static constexpr bool value = false; }; template struct AllowGenericTrySeqTraits> { static constexpr bool value = false; }; template struct TakeValueExists { static constexpr bool value = false; }; template struct TakeValueExists()))>> { static constexpr bool value = true; }; // If there exists a function 'IsStatusOk(const T&) -> bool' then we assume that // T is a status type for the purposes of promise sequences, and a non-OK T // should terminate the sequence and return. template struct TrySeqTraitsWithSfinae< T, absl::enable_if_t< std::is_same())), bool>::value && !TakeValueExists::value && AllowGenericTrySeqTraits::value, void>> { using UnwrappedType = void; using WrappedType = T; template static auto CallFactory(Next* next, T&&) { return next->Make(); } static bool IsOk(const T& status) { return IsStatusOk(status); } static std::string ErrorString(const T& status) { return IsStatusOk(status) ? "OK" : "FAILED"; } template static R ReturnValue(T&& status) { return FailureStatusCast(std::move(status)); } template static Poll CheckResultAndRunNext(T prior, RunNext run_next) { if (!IsStatusOk(prior)) return Result(std::move(prior)); return run_next(std::move(prior)); } }; template struct TrySeqTraitsWithSfinae< T, absl::enable_if_t< std::is_same())), bool>::value && TakeValueExists::value && AllowGenericTrySeqTraits::value, void>> { using UnwrappedType = decltype(TakeValue(std::declval())); using WrappedType = T; template static auto CallFactory(Next* next, T&& status) { return next->Make(TakeValue(std::forward(status))); } static bool IsOk(const T& status) { return IsStatusOk(status); } static std::string ErrorString(const T& status) { return IsStatusOk(status) ? "OK" : "FAILED"; } template static R ReturnValue(T&& status) { DCHECK(!IsStatusOk(status)); return FailureStatusCast(status.status()); } template static Poll CheckResultAndRunNext(T prior, RunNext run_next) { if (!IsStatusOk(prior)) return Result(std::move(prior)); return run_next(std::move(prior)); } }; template <> struct TrySeqTraitsWithSfinae { using UnwrappedType = void; using WrappedType = absl::Status; template static auto CallFactory(Next* next, absl::Status&&) { return next->Make(); } static bool IsOk(const absl::Status& status) { return status.ok(); } static std::string ErrorString(const absl::Status& status) { return status.ToString(); } template static R ReturnValue(absl::Status&& status) { return FailureStatusCast(std::move(status)); } template static Poll CheckResultAndRunNext(absl::Status prior, RunNext run_next) { if (!prior.ok()) return StatusCast(std::move(prior)); return run_next(std::move(prior)); } }; template using TrySeqTraits = TrySeqTraitsWithSfinae; template class TrySeq { public: explicit TrySeq(P&& promise, Fs&&... factories, DebugLocation whence) : state_(std::forward

(promise), std::forward(factories)..., whence) {} auto operator()() { return state_.PollOnce(); } private: SeqState state_; }; template struct TrySeqIterTraits { using Iter = I; using Factory = F; using Argument = Arg; using IterValue = decltype(*std::declval()); using StateCreated = decltype(std::declval()(std::declval(), std::declval())); using State = PromiseLike; using Wrapped = typename State::Result; using Traits = TrySeqTraits; }; template struct TrySeqIterResultTraits { using IterTraits = TrySeqIterTraits; using Result = BasicSeqIter; }; } // namespace promise_detail // Try a sequence of operations. // * Run the first functor as a promise. // * Feed its success result into the second functor to create a promise, // then run that. // * ... // * Feed the second-final success result into the final functor to create a // promise, then run that, with the overall success result being that // promises success result. // If any step fails, fail everything. // Functors can return StatusOr<> to signal that a value is fed forward, or // Status to indicate only success/failure. In the case of returning Status, // the construction functors take no arguments. template F TrySeq(F functor) { return functor; } template promise_detail::TrySeq TrySeq(F0 f0, F1 f1, DebugLocation whence = {}) { return promise_detail::TrySeq(std::move(f0), std::move(f1), whence); } template promise_detail::TrySeq TrySeq(F0 f0, F1 f1, F2 f2, DebugLocation whence = {}) { return promise_detail::TrySeq(std::move(f0), std::move(f1), std::move(f2), whence); } template promise_detail::TrySeq TrySeq(F0 f0, F1 f1, F2 f2, F3 f3, DebugLocation whence = {}) { return promise_detail::TrySeq( std::move(f0), std::move(f1), std::move(f2), std::move(f3), whence); } template promise_detail::TrySeq TrySeq(F0 f0, F1 f1, F2 f2, F3 f3, F4 f4, DebugLocation whence = {}) { return promise_detail::TrySeq( std::move(f0), std::move(f1), std::move(f2), std::move(f3), std::move(f4), whence); } template promise_detail::TrySeq TrySeq( F0 f0, F1 f1, F2 f2, F3 f3, F4 f4, F5 f5, DebugLocation whence = {}) { return promise_detail::TrySeq( std::move(f0), std::move(f1), std::move(f2), std::move(f3), std::move(f4), std::move(f5), whence); } template promise_detail::TrySeq TrySeq( F0 f0, F1 f1, F2 f2, F3 f3, F4 f4, F5 f5, F6 f6, DebugLocation whence = {}) { return promise_detail::TrySeq( std::move(f0), std::move(f1), std::move(f2), std::move(f3), std::move(f4), std::move(f5), std::move(f6), whence); } template promise_detail::TrySeq TrySeq( F0 f0, F1 f1, F2 f2, F3 f3, F4 f4, F5 f5, F6 f6, F7 f7, DebugLocation whence = {}) { return promise_detail::TrySeq( std::move(f0), std::move(f1), std::move(f2), std::move(f3), std::move(f4), std::move(f5), std::move(f6), std::move(f7), whence); } // Try a sequence of operations of unknown length. // Asynchronously: // for (element in (begin, end)) { // auto r = wait_for factory(element, argument); // if (!r.ok()) return r; // argument = *r; // } // return argument; template typename promise_detail::TrySeqIterResultTraits::Result TrySeqIter(Iter begin, Iter end, Argument argument, Factory factory) { using Result = typename promise_detail::TrySeqIterResultTraits::Result; return Result(begin, end, std::move(factory), std::move(argument)); } } // namespace grpc_core #endif // GRPC_SRC_CORE_LIB_PROMISE_TRY_SEQ_H