feat add status and status_or #4

Merged
tqcq merged 5 commits from feat/add_status into develop 2024-01-12 14:05:04 +08:00
6 changed files with 1094 additions and 68 deletions

View File

@ -1,6 +1,9 @@
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
project(ulib LANGUAGES CXX C VERSION 0.1.0) project(
ulib
LANGUAGES CXX C
VERSION 0.1.0)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF) set(CMAKE_C_EXTENSIONS OFF)
@ -23,40 +26,41 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if(ULIB_SHARED_LIB) if(ULIB_SHARED_LIB)
add_library(${PROJECT_NAME} SHARED "") add_library(${PROJECT_NAME} SHARED "")
else() else()
add_library(${PROJECT_NAME} STATIC "" add_library(${PROJECT_NAME} STATIC "" src/ulib/system/timer.cpp
src/ulib/system/timer.cpp src/ulib/system/timer.h)
src/ulib/system/timer.h)
endif() endif()
target_sources(${PROJECT_NAME} PRIVATE target_sources(
3party/mongoose/mongoose.c ${PROJECT_NAME}
src/ulib/base/location.h PRIVATE 3party/mongoose/mongoose.c
src/ulib/base/location.cpp src/ulib/base/location.h
src/ulib/concorrency/barrier.cpp src/ulib/base/location.cpp
src/ulib/concorrency/barrier.h src/ulib/status.h
src/ulib/concorrency/mutex.cpp src/ulib/status.cpp
src/ulib/concorrency/mutex.h src/ulib/concorrency/barrier.cpp
src/ulib/concorrency/condition_variable.cpp src/ulib/concorrency/barrier.h
src/ulib/concorrency/condition_variable.h src/ulib/concorrency/mutex.cpp
src/ulib/concorrency/countdown_latch.cpp src/ulib/concorrency/mutex.h
src/ulib/concorrency/countdown_latch.h src/ulib/concorrency/condition_variable.cpp
src/ulib/concorrency/internal/mutex_impl.cpp src/ulib/concorrency/condition_variable.h
src/ulib/concorrency/internal/mutex_impl.h src/ulib/concorrency/countdown_latch.cpp
src/ulib/concorrency/internal/condition_variable_impl.cpp src/ulib/concorrency/countdown_latch.h
src/ulib/concorrency/internal/condition_variable_impl.h src/ulib/concorrency/internal/mutex_impl.cpp
src/ulib/concorrency/event.cpp src/ulib/concorrency/internal/mutex_impl.h
src/ulib/concorrency/event.h src/ulib/concorrency/internal/condition_variable_impl.cpp
src/ulib/system/thread.h src/ulib/concorrency/internal/condition_variable_impl.h
src/ulib/system/thread.cpp src/ulib/concorrency/event.cpp
src/ulib/system/thread_pool.h src/ulib/concorrency/event.h
src/ulib/system/thread_pool.cpp src/ulib/system/thread.h
src/ulib/system/timer.h src/ulib/system/thread.cpp
src/ulib/system/timer.cpp src/ulib/system/thread_pool.h
) src/ulib/system/thread_pool.cpp
src/ulib/system/timer.h
src/ulib/system/timer.cpp)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
## CMP0063 # CMP0063
if(POLICY CMP0063) if(POLICY CMP0063)
cmake_policy(SET CMP0063 NEW) cmake_policy(SET CMP0063 NEW)
endif() endif()
@ -64,9 +68,15 @@ if(POLICY CMP0048)
cmake_policy(SET CMP0048 NEW) cmake_policy(SET CMP0048 NEW)
endif() endif()
set(FMT_USE_CPP11 OFF CACHE BOOL "Use C++11" FORCE) set(FMT_USE_CPP11
set(FMT_TEST OFF CACHE BOOL "Build tests" FORCE) OFF
set(FMT_USE_CPP11 OFF CACHE BOOL "Use C++11" FORCE) CACHE BOOL "Use C++11" FORCE)
set(FMT_TEST
OFF
CACHE BOOL "Build tests" FORCE)
set(FMT_USE_CPP11
OFF
CACHE BOOL "Use C++11" FORCE)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/3party/fmt) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/3party/fmt)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/3party/sqlpp11) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/3party/sqlpp11)
@ -77,24 +87,22 @@ set(BUILD_SHARED_LIBS OFF)
set(BUILD_OBJECT_LIBS OFF) set(BUILD_OBJECT_LIBS OFF)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/3party/jsoncpp) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/3party/jsoncpp)
target_sources(${PROJECT_NAME} PRIVATE target_sources(
src/ulib/empty.cpp ${PROJECT_NAME} PRIVATE src/ulib/empty.cpp src/ulib/log/logger.cpp
src/ulib/log/logger.cpp src/ulib/log/log.cpp src/ulib/log/level.cpp)
src/ulib/log/log.cpp target_link_libraries(${PROJECT_NAME} PUBLIC fmt::fmt jsoncpp_static
src/ulib/log/level.cpp sqlpp11::sqlpp11)
)
target_link_libraries(${PROJECT_NAME} PUBLIC fmt::fmt jsoncpp_static sqlpp11::sqlpp11)
target_compile_definitions(${PROJECT_NAME} PRIVATE ULIB_LIBRARY_IMPL) target_compile_definitions(${PROJECT_NAME} PRIVATE ULIB_LIBRARY_IMPL)
target_include_directories(${PROJECT_NAME} PUBLIC target_include_directories(
3party/inja ${PROJECT_NAME}
3party/mongoose PUBLIC 3party/inja
3party/nlohmann 3party/mongoose
3party/nonstd 3party/nlohmann
3party/sigslot 3party/nonstd
3party/rxcpp/Rx/v2/src 3party/sigslot
3party/rxcpp/Ix/CPP/src 3party/rxcpp/Rx/v2/src
src 3party/rxcpp/Ix/CPP/src
) src)
install(TARGETS ${PROJECT_NAME} DESTINATION lib) install(TARGETS ${PROJECT_NAME} DESTINATION lib)

253
src/ulib/status.cpp Normal file
View File

@ -0,0 +1,253 @@
// Copyright 2018 Google LLC
//
// 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
//
// https://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.
#include "status.h"
#include <sstream>
#include <string>
#include <unordered_map>
namespace ulib {
namespace {
std::string
StatusWhat(Status const &status)
{
std::ostringstream os;
os << status;
return std::move(os).str();
}
}// namespace
std::string
StatusCodeToString(StatusCode code)
{
switch (code) {
case StatusCode::kOk:
return "OK";
case StatusCode::kCancelled:
return "CANCELLED";
case StatusCode::kUnknown:
return "UNKNOWN";
case StatusCode::kInvalidArgument:
return "INVALID_ARGUMENT";
case StatusCode::kDeadlineExceeded:
return "DEADLINE_EXCEEDED";
case StatusCode::kNotFound:
return "NOT_FOUND";
case StatusCode::kAlreadyExists:
return "ALREADY_EXISTS";
case StatusCode::kPermissionDenied:
return "PERMISSION_DENIED";
case StatusCode::kUnauthenticated:
return "UNAUTHENTICATED";
case StatusCode::kResourceExhausted:
return "RESOURCE_EXHAUSTED";
case StatusCode::kFailedPrecondition:
return "FAILED_PRECONDITION";
case StatusCode::kAborted:
return "ABORTED";
case StatusCode::kOutOfRange:
return "OUT_OF_RANGE";
case StatusCode::kUnimplemented:
return "UNIMPLEMENTED";
case StatusCode::kInternal:
return "INTERNAL";
case StatusCode::kUnavailable:
return "UNAVAILABLE";
case StatusCode::kDataLoss:
return "DATA_LOSS";
default: {
std::stringstream ss;
ss << "UNEXPECTED_STATUS_CODE=" << static_cast<int>(code);
return ss.str();
}
}
}
std::ostream &
operator<<(std::ostream &os, StatusCode code)
{
return os << StatusCodeToString(code);
}
bool
operator==(ErrorInfo const &a, ErrorInfo const &b)
{
return a.reason_ == b.reason_ && a.domain_ == b.domain_
&& a.metadata_ == b.metadata_;
}
bool
operator!=(ErrorInfo const &a, ErrorInfo const &b)
{
return !(a == b);
}
// Encapsulates the implementation of a non-OK status. OK Statuses are
// represented by a nullptr Status::impl_, as an optimization for the common
// case of OK Statuses. This class holds all the data associated with a non-OK
// Status so we don't have to worry about bloating the common OK case.
class Status::Impl {
public:
using PayloadType = std::unordered_map<std::string, std::string>;
explicit Impl(StatusCode code,
std::string message,
ErrorInfo info,
PayloadType payload)
: code_(code),
message_(std::move(message)),
error_info_(std::move(info)),
payload_(std::move(payload))
{}
StatusCode code() const { return code_; }
std::string const &message() const { return message_; }
ErrorInfo const &error_info() const { return error_info_; }
PayloadType const &payload() const { return payload_; }
// Allows mutable access to payload, which is needed in the
// `internal::SetPayload()` function.
PayloadType &payload() { return payload_; }
friend inline bool operator==(Impl const &a, Impl const &b)
{
return a.code_ == b.code_ && a.message_ == b.message_
&& a.error_info_ == b.error_info_ && a.payload_ == b.payload_;
}
friend inline bool operator!=(Impl const &a, Impl const &b)
{
return !(a == b);
}
private:
StatusCode code_;
std::string message_;
ErrorInfo error_info_;
PayloadType payload_;
};
Status::Status() = default;
Status::~Status() = default;
Status::Status(Status &&) noexcept = default;
Status &Status::operator=(Status &&) noexcept = default;
// Deep copy
Status::Status(Status const &other)
: impl_(other.ok() ? nullptr : new auto(*other.impl_))
{}
// Deep copy
Status &
Status::operator=(Status const &other)
{
impl_.reset(other.ok() ? nullptr : new auto(*other.impl_));
return *this;
}
// OK statuses have an impl_ == nullptr. Non-OK Statuses get an Impl.
Status::Status(StatusCode code, std::string message, ErrorInfo info)
: impl_(code == StatusCode::kOk
? nullptr
: new Status::Impl{code, std::move(message), std::move(info),
Status::Impl::PayloadType()})
{}
StatusCode
Status::code() const
{
return impl_ ? impl_->code() : StatusCode::kOk;
}
std::string const &
Status::message() const
{
static auto const *const kEmpty = new std::string{};
return impl_ ? impl_->message() : *kEmpty;
}
ErrorInfo const &
Status::error_info() const
{
static auto const *const kEmpty = new ErrorInfo{};
return impl_ ? impl_->error_info() : *kEmpty;
}
bool
Status::Equals(Status const &a, Status const &b)
{
return (a.ok() && b.ok()) || (a.impl_ && b.impl_ && *a.impl_ == *b.impl_);
}
std::ostream &
operator<<(std::ostream &os, Status const &s)
{
if (s.ok()) return os << StatusCode::kOk;
os << s.code() << ": " << s.message();
auto const &e = s.error_info();
if (e.reason().empty() && e.domain().empty() && e.metadata().empty()) {
return os;
}
os << " error_info={reason=" << e.reason();
os << ", domain=" << e.domain();
os << ", metadata={";
char const *sep = "";
for (auto const &item : e.metadata()) {
os << sep << item.first << "=" << item.second;
sep = ", ";
}
return os << "}}";
}
namespace internal {
void
AddMetadata(ErrorInfo &ei, std::string const &key, std::string value)
{
ei.metadata_[key] = std::move(value);
}
// Sets the given `payload`, indexed by the given `key`, on the given `Status`,
// IFF the status is not OK. Payloads are considered in equality comparisons.
// The keyspace used here is separate from other keyspaces (e.g.,
// `absl::Status`), so we only need to coordinate keys with ourselves.
void
SetPayload(Status &s, std::string key, std::string payload)
{
if (s.impl_) s.impl_->payload()[std::move(key)] = std::move(payload);
}
// Returns the payload associated with the given `key`, if available.
ulib::optional<std::string>
GetPayload(Status const &s, std::string const &key)
{
if (!s.impl_) return ulib::nullopt;
auto const &payload = s.impl_->payload();
auto it = payload.find(key);
if (it == payload.end()) return ulib::nullopt;
return it->second;
}
}// namespace internal
RuntimeStatusError::RuntimeStatusError(Status status)
: std::runtime_error(StatusWhat(status)),
status_(std::move(status))
{}
}// namespace ulib

401
src/ulib/status.h Normal file
View File

@ -0,0 +1,401 @@
// Copyright 2018 Google LLC
//
// 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
//
// https://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 GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STATUS_H
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STATUS_H
#include "optional.h"
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
namespace ulib {
/**
* Well-known status codes with `grpc::StatusCode`-compatible values.
*
* The semantics of these values are documented in:
* https://grpc.io/grpc/cpp/classgrpc_1_1_status.html
*/
enum class StatusCode {
/// Not an error; returned on success.
kOk = 0,
/// `kCancelled` (gRPC code `CANCELLED`) indicates the operation was
/// cancelled, typically by the caller.
kCancelled = 1,
/// `kUnknown` (gRPC code `UNKNOWN`) indicates an unknown error occurred.
///
/// In general, more specific errors should be raised, if possible. Errors
/// raised by APIs that do not return enough error information may be
/// converted to this error.
kUnknown = 2,
/// `kInvalidArgument` (gRPC code `INVALID_ARGUMENT`) indicates the caller
/// specified an invalid argument, such as a malformed filename.
///
/// Note that use of such errors should be narrowly limited to indicate the
/// invalid nature of the arguments themselves. Errors with validly formed
/// arguments that may cause errors with the state of the receiving system
/// should be denoted with `kFailedPrecondition` instead.
kInvalidArgument = 3,
/// `kDeadlineExceeded` (gRPC code `DEADLINE_EXCEEDED`) indicates a deadline
/// expired before the operation could complete.
///
/// For operations that may change state within a system, this error may be
/// returned even if the operation has completed successfully. For example, a
/// successful response from a server could have been delayed long enough for
/// the deadline to expire.
kDeadlineExceeded = 4,
/// `kNotFound` (gRPC code `NOT_FOUND`) indicates some requested entity (such
/// as a file or directory) was not found.
///
/// `kNotFound` is useful if a request should be denied for an entire class of
/// users, such as during a gradual feature rollout or undocumented allow
/// list.
/// If a request should be denied for specific sets of users, such as through
/// user-based access control, use `kPermissionDenied` instead.
kNotFound = 5,
/// `kAlreadyExists` (gRPC code `ALREADY_EXISTS`) indicates that the entity a
/// caller attempted to create (such as a file or directory) is already
/// present.
kAlreadyExists = 6,
/// `kPermissionDenied` (gRPC code `PERMISSION_DENIED`) indicates that the
/// caller does not have permission to execute the specified operation.
///
/// Note that this error is different than an error due to an
/// *un*authenticated caller. This error code does not imply the request is
/// valid or the requested entity exists or satisfies any other
/// pre-conditions.
///
/// `kPermissionDenied` must not be used for rejections caused by exhausting
/// some resource. Instead, use `kResourceExhausted` for those errors.
/// `kPermissionDenied` must not be used if the caller cannot be identified.
/// Instead, use `kUnauthenticated` for those errors.
kPermissionDenied = 7,
/// `kResourceExhausted` (gRPC code `RESOURCE_EXHAUSTED`) indicates some
/// resource has been exhausted.
///
/// Examples include a per-user quota, or the entire file system being out of
/// space.
kResourceExhausted = 8,
/// `kFailedPrecondition` (gRPC code `FAILED_PRECONDITION`) indicates that the
/// operation was rejected because the system is not in a state required for
/// the operation's execution.
///
/// For example, a directory to be deleted may be non-empty, a "rmdir"
/// operation is applied to a non-directory, etc.
///
/// Some guidelines that may help a service implementer in deciding between
/// `kFailedPrecondition`, `kAborted`, and `kUnavailable`:
///
/// 1. Use `kUnavailable` if the client can retry just the failing call.
/// 2. Use `kAborted` if the client should retry at a higher transaction
/// level (such as when a client-specified test-and-set fails, indicating
/// the client should restart a read-modify-write sequence).
/// 3. Use `kFailedPrecondition` if the client should not retry until the
/// system state has been explicitly fixed. For example, if a "rmdir" fails
/// because the directory is non-empty, `kFailedPrecondition` should be
/// returned since the client should not retry unless the files are deleted
/// from the directory.
kFailedPrecondition = 9,
/// `kAborted` (gRPC code `ABORTED`) indicates the operation was aborted.
///
/// This is typically due to a concurrency issue such as a sequencer check
/// failure or a failed transaction.
///
/// See the guidelines above for deciding between `kFailedPrecondition`,
/// `kAborted`, and `kUnavailable`.
kAborted = 10,
/// `kOutOfRange` (gRPC code `OUT_OF_RANGE`) indicates the operation was
/// attempted past the valid range, such as seeking or reading past an
/// end-of-file.
///
/// Unlike `kInvalidArgument`, this error indicates a problem that may
/// be fixed if the system state changes. For example, a 32-bit file
/// system will generate `kInvalidArgument` if asked to read at an
/// offset that is not in the range [0,2^32-1], but it will generate
/// `kOutOfRange` if asked to read from an offset past the current
/// file size.
///
/// There is a fair bit of overlap between `kFailedPrecondition` and
/// `kOutOfRange`. We recommend using `kOutOfRange` (the more specific
/// error) when it applies so that callers who are iterating through
/// a space can easily look for an `kOutOfRange` error to detect when
/// they are done.
kOutOfRange = 11,
/// `kUnimplemented` (gRPC code `UNIMPLEMENTED`) indicates the operation is
/// not implemented or supported in this service.
///
/// In this case, the operation should not be re-attempted.
kUnimplemented = 12,
/// `kInternal` (gRPC code `INTERNAL`) indicates an internal error has
/// occurred and some invariants expected by the underlying system have not
/// been satisfied.
///
/// While this error code is reserved for serious errors, some services return
/// this error under overload conditions.
kInternal = 13,
/// `kUnavailable` (gRPC code `UNAVAILABLE`) indicates the service is
/// currently unavailable and that this is most likely a transient condition.
///
/// An error such as this can be corrected by retrying with a backoff scheme.
/// Note that it is not always safe to retry non-idempotent operations.
///
/// See the guidelines above for deciding between `kFailedPrecondition`,
/// `kAborted`, and `kUnavailable`.
kUnavailable = 14,
/// `kDataLoss` (gRPC code `DATA_LOSS`) indicates that unrecoverable data loss
/// or corruption has occurred.
///
/// As this error is serious, proper alerting should be attached to errors
/// such as this.
kDataLoss = 15,
/// `kUnauthenticated` (gRPC code `UNAUTHENTICATED`) indicates that the
/// request does not have valid authentication credentials for the operation.
///
/// Correct the authentication and try again.
kUnauthenticated = 16,
};
/// Convert @p code to a human readable string.
std::string StatusCodeToString(StatusCode code);
/// Integration with `std::iostreams`.
std::ostream &operator<<(std::ostream &os, StatusCode code);
class Status;
class ErrorInfo;
namespace internal {
void AddMetadata(ErrorInfo &, std::string const &key, std::string value);
void SetPayload(Status &, std::string key, std::string payload);
ulib::optional<std::string> GetPayload(Status const &, std::string const &key);
}// namespace internal
/**
* Describes the cause of the error with structured details.
*
* @see https://cloud.google.com/apis/design/errors#error_info
*/
class ErrorInfo {
public:
/**
* Default constructor.
*
* Post-condition: the `reason()`, `domain()`, and `metadata()` fields are
* empty.
*/
ErrorInfo() = default;
/**
* Constructor.
*
* @param reason initializes the `reason()` value.
* @param domain initializes the `domain()` value.
* @param metadata initializes the `metadata()` value.
*/
explicit ErrorInfo(std::string reason,
std::string domain,
std::unordered_map<std::string, std::string> metadata)
: reason_(std::move(reason)),
domain_(std::move(domain)),
metadata_(std::move(metadata))
{}
/**
* The reason of the error.
*
* This is a constant value that identifies the proximate cause of the error.
* Error reasons are unique within a particular domain of errors. This should
* be at most 63 characters and match a regular expression of
* `[A-Z][A-Z0-9_]+[A-Z0-9]`, which represents UPPER_SNAKE_CASE.
*/
std::string const &reason() const { return reason_; }
/**
* The logical grouping to which the "reason" belongs.
*
* The error domain is typically the registered service name of the tool or
* product that generates the error. Example: "pubsub.googleapis.com". If the
* error is generated by some common infrastructure, the error domain must be
* a globally unique value that identifies the infrastructure. For Google API
* infrastructure, the error domain is "googleapis.com".
*
* For errors generated by the C++ client libraries the domain is
* `gcloud-cpp`.
*/
std::string const &domain() const { return domain_; }
/**
* Additional structured details about this error.
*
* Keys should match the regular expression `[a-zA-Z0-9-_]` and be limited
* to 64 characters in length.
*
* When identifying the current value of an exceeded limit, the units should
* be contained in the key, not the value. For example, if the client exceeds
* the number of instances that can be created in a single (batch) request
* return `{"instanceLimitPerRequest": "100"}` rather than
* `{"instanceLimit": "100/request"}`.
*/
std::unordered_map<std::string, std::string> const &metadata() const
{
return metadata_;
}
friend bool operator==(ErrorInfo const &, ErrorInfo const &);
friend bool operator!=(ErrorInfo const &, ErrorInfo const &);
private:
friend void internal::AddMetadata(ErrorInfo &,
std::string const &key,
std::string value);
std::string reason_;
std::string domain_;
std::unordered_map<std::string, std::string> metadata_;
};
/**
* Represents success or an error with info about the error.
*
* This class is typically used to indicate whether or not a function or other
* operation completed successfully. Success is indicated by an "OK" status. OK
* statuses will have `.code() == StatusCode::kOk` and `.ok() == true`, with
* all other properties having empty values. All OK statuses are equal. Any
* non-OK `Status` is considered an error. Users can inspect the error using
* the member functions, or they can simply stream the `Status` object, and it
* will print itself in some human readable way (the streamed format may change
* over time and you should *not* depend on the specific format of a streamed
* `Status` object remaining unchanged).
*
* This is a regular value type that can be copied, moved, compared for
* equality, and streamed.
*/
class Status {
public:
/// Default constructor, initializes to `StatusCode::kOk`.
Status();
/// Destructor.
~Status();
///@{
/**
* @name Copy construction and assignment.
*/
Status(Status const &);
Status &operator=(Status const &);
///@}
///@{
/**
* @name Move construction and assignment.
*/
Status(Status &&) noexcept;
Status &operator=(Status &&) noexcept;
///@}
/**
* Construct from a status code, message and (optional) error info.
*
* @param code the status code for the new `Status`.
* @param message the message for the new `Status`, ignored if @p code is
* `StatusCode::kOk`.
* @param info the `ErrorInfo` for the new `Status`, ignored if @p code is
* `SStatusCode::kOk`.
*/
explicit Status(StatusCode code, std::string message, ErrorInfo info = {});
/// Returns true if the status code is `StatusCode::kOk`.
bool ok() const { return !impl_; }
/// Returns the status code.
StatusCode code() const;
/**
* Returns the message associated with the status.
*
* This is always empty if `code()` is `StatusCode::kOk`.
*/
std::string const &message() const;
/**
* Returns the additional error info associated with the status.
*
* This is always a default-constructed error info if `code()` is
* `StatusCode::kOk`.
*/
ErrorInfo const &error_info() const;
friend inline bool operator==(Status const &a, Status const &b)
{
return (a.ok() && b.ok()) || Equals(a, b);
}
friend inline bool operator!=(Status const &a, Status const &b)
{
return !(a == b);
}
private:
static bool Equals(Status const &a, Status const &b);
friend void internal::SetPayload(Status &, std::string, std::string);
friend ulib::optional<std::string>
internal::GetPayload(Status const &, std::string const &);
class Impl;
// A null `impl_` is an OK status. Only non-OK Statuses allocate an Impl.
std::unique_ptr<Impl> impl_;
};
/**
* Stream @p s to @p os.
*
* This in intended for logging and troubleshooting. Applications should not
* depend on the format of this output.
*/
std::ostream &operator<<(std::ostream &os, Status const &s);
/**
* A runtime error that wraps a `google::cloud::Status`.
*/
class RuntimeStatusError : public std::runtime_error {
public:
/// Constructor from a `Status`.
explicit RuntimeStatusError(Status status);
/// Returns the original status.
Status const &status() const { return status_; }
private:
Status status_;
};
}// namespace ulib
#endif// GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STATUS_H

355
src/ulib/status_or.h Normal file
View File

@ -0,0 +1,355 @@
// Copyright 2018 Google LLC
//
// 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
//
// https://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 GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STATUS_OR_H
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STATUS_OR_H
#include "status.h"
#include <stdexcept>
#include <type_traits>
#include <utility>
namespace ulib {
/**
* Holds a value or a `Status` indicating why there is no value.
*
* `StatusOr<T>` represents either a usable `T` value or a `Status` object
* explaining why a `T` value is not present. Typical usage of `StatusOr<T>`
* looks like usage of a smart pointer, or even a `std::optional<T>`, in that
* you first check its validity using a conversion to bool (or by calling
* `StatusOr::ok()`), then you may dereference the object to access the
* contained value.
*
* It is undefined behavior (UB) to dereference a `StatusOr<T>` that is not
* "ok". For example:
*
* @code
* StatusOr<Foo> foo = FetchFoo();
* if (!foo) { // Same as !foo.ok()
* // handle error and probably look at foo.status()
* } else {
* foo->DoSomethingFooey(); // UB if !foo
* }
* @endcode
*
* Alternatively, you may call the `StatusOr::value()` member function,
* which is defined to: (1) throw an exception if there is no `T` value, or (2)
* crash the program if exceptions are disabled. It is never UB to call
* `value()`.
*
* @code
* StatusOr<Foo> foo = FetchFoo();
* foo.value().DoSomethingFooey(); // May throw/crash if there is no value
* @endcode
*
* Functions that can fail will often return a `StatusOr<T>` instead of
* returning an error code and taking a `T` out-param, or rather than directly
* returning the `T` and throwing an exception on error. `StatusOr<T>` is used
* so that callers can choose whether they want to explicitly check for errors,
* crash the program, or throw exceptions.
*
* Since constructors do not have a return value, they should be designed in
* such a way that they cannot fail by moving the object's complex
* initialization logic into a separate factory function that itself can return
* a `StatusOr<T>`. For example:
*
* @code
* class Bar {
* public:
* Bar(Arg arg);
* ...
* };
* StatusOr<Bar> MakeBar() {
* ... complicated logic that might fail
* return Bar(std::move(arg));
* }
* @endcode
*
* `StatusOr<T>` supports equality comparisons if the underlying type `T` does.
*
* @tparam T the type of the value.
*/
template<typename T>
class StatusOr final {
public:
static_assert(!std::is_reference<T>::value,
"StatusOr<T> requires T to **not** be a reference type");
/**
* A `value_type` member for use in generic programming.
*
* This is analogous to that of `std::optional::value_type`.
*/
using value_type = T;
/**
* Initializes with an error status (`StatusCode::kUnknown`).
*/
StatusOr() : StatusOr(MakeDefaultStatus()) {}
StatusOr(StatusOr const &) = default;
StatusOr &operator=(StatusOr const &) = default;
// NOLINTNEXTLINE(performance-noexcept-move-constructor)
StatusOr(StatusOr &&other)
: status_(std::move(other.status_)),
value_(std::move(other.value_))
{
other.status_ = MakeDefaultStatus();
}
// NOLINTNEXTLINE(performance-noexcept-move-constructor)
StatusOr &operator=(StatusOr &&other)
{
status_ = std::move(other.status_);
value_ = std::move(other.value_);
other.status_ = MakeDefaultStatus();
return *this;
}
/**
* Creates a new `StatusOr<T>` holding the error condition @p rhs.
*
* @par Post-conditions
* `ok() == false` and `status() == rhs`.
*
* @param rhs the status to initialize the object.
* @throws std::invalid_argument if `rhs.ok()`. If exceptions are disabled the
* program terminates via `google::cloud::Terminate()`
*/
// NOLINTNEXTLINE(google-explicit-constructor)
StatusOr(Status rhs) : status_(std::move(rhs))
{
if (status_.ok()) {
// google::cloud::internal::ThrowInvalidArgument(__func__);
throw std::invalid_argument(__func__);
}
}
/**
* Assigns the given non-OK Status to this `StatusOr<T>`.
*
* @throws std::invalid_argument if `status.ok()`. If exceptions are disabled
* the program terminates via `google::cloud::Terminate()`
*/
StatusOr &operator=(Status status)
{
*this = StatusOr(std::move(status));
return *this;
}
/**
* Assign a `T` (or anything convertible to `T`) into the `StatusOr`.
*
* This function does not participate in overload resolution if `U` is equal
* to `StatusOr<T>` (or to a cv-ref-qualified `StatusOr<T>`).
*
* @return a reference to this object.
* @tparam U a type convertible to @p T.
*/
template<typename U = T,
/// @cond implementation_details
std::enable_if_t<!std::is_same<StatusOr, std::decay_t<U>>::value,
int> = 0
/// @endcond
>
StatusOr &operator=(U &&rhs)
{
status_ = Status();
value_ = std::forward<U>(rhs);
return *this;
}
/**
* Creates a new `StatusOr<T>` holding the value @p rhs.
*
* @par Post-conditions
* `ok() == true` and `value() == rhs`.
*
* @param rhs the value used to initialize the object.
*
* @throws ... If `T`'s move constructor throws.
*/
// NOLINTNEXTLINE(google-explicit-constructor)
StatusOr(T &&rhs) : value_(std::move(rhs)) {}
/**
* Creates a new `StatusOr<T>` holding the value @p rhs.
*
* @par Post-conditions
* `ok() == true` and `value() == rhs`.
*
* @param rhs the value used to initialize the object.
*
* @throws ... If `T` copy constructor throws.
*/
// NOLINTNEXTLINE(google-explicit-constructor)
StatusOr(T const &rhs) : value_(rhs) {}
/// Returns `true` when `this` holds a value.
bool ok() const { return status_.ok(); }
/// Returns `true` when `this` holds a value.
explicit operator bool() const { return status_.ok(); }
///@{
/**
* @name Dereference operators.
*
* @par Pre-conditions
* @parblock
* `ok() == true`
*
* @warning Using these operators when `ok() == false` results in undefined
* behavior.
* @endparblock
*
* @return A properly ref and const-qualified reference to the underlying
* value.
*/
T &operator*() & { return *value_; }
T const &operator*() const & { return *value_; }
T &&operator*() && { return *std::move(value_); }
T const &&operator*() const && { return *std::move(value_); }
///@}
///@{
/**
* @name Member access operators.
*
* @par Pre-conditions
* @parblock
* `ok() == true`
*
* @warning Using these operators when `ok() == false` results in undefined
* behavior.
* @endparblock
*
* @return A properly ref and const-qualified pointer to the underlying value.
*/
T *operator->() & { return &*value_; }
T const *operator->() const & { return &*value_; }
///@}
///@{
/**
* @name Value accessors.
*
* @return All these member functions return a (properly ref and
* const-qualified) reference to the underlying value.
*
* @throws RuntimeStatusError with the contents of `status()` if the object
* does not contain a value, i.e., if `ok() == false`.
*/
T &value() &
{
CheckHasValue();
return **this;
}
T const &value() const &
{
CheckHasValue();
return **this;
}
T &&value() &&
{
CheckHasValue();
return std::move(**this);
}
T const &&value() const &&
{
CheckHasValue();
return std::move(**this);
}
///@}
///@{
/**
* @name Status accessors.
*
* @return A reference to the contained `Status`.
*/
Status const &status() const & { return status_; }
Status &&status() && { return std::move(status_); }
///@}
private:
static Status MakeDefaultStatus()
{
return Status{StatusCode::kUnknown, "default"};
}
void CheckHasValue() const &
{
if (!ok()) {
// internal::ThrowStatus(status_);
throw RuntimeStatusError(std::move(status_));
}
}
// When possible, do not copy the status.
void CheckHasValue() &&
{
if (!ok()) {
// internal::ThrowStatus(std::move(status_));
throw RuntimeStatusError(std::move(status_));
}
}
Status status_;
ulib::optional<T> value_;
};
// Returns true IFF both `StatusOr<T>` objects hold an equal `Status` or an
// equal instance of `T`. This function requires that `T` supports equality.
template<typename T>
bool
operator==(StatusOr<T> const &a, StatusOr<T> const &b)
{
if (!a || !b) return a.status() == b.status();
return *a == *b;
}
// Returns true of `a` and `b` are not equal. See `operator==` docs above for
// the definition of equal.
template<typename T>
bool
operator!=(StatusOr<T> const &a, StatusOr<T> const &b)
{
return !(a == b);
}
template<typename T>
StatusOr<T>
make_status_or(T rhs)
{
return StatusOr<T>(std::move(rhs));
}
}// namespace ulib
#endif// GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STATUS_OR_H

View File

@ -3,23 +3,21 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_STANDARD_REQUIRED ON)
add_executable(ulib_test add_executable(
ulib/base/types_unittest.cpp ulib_test
ulib/log/log_unittest.cpp ulib/base/types_unittest.cpp
ulib/concorrency/mutex_unittest.cpp ulib/log/log_unittest.cpp
ulib/concorrency/event_unittest.cpp ulib/concorrency/mutex_unittest.cpp
ulib/concorrency/countdown_latch_unittest.cpp ulib/concorrency/event_unittest.cpp
ulib/system/thread_unittest.cpp ulib/concorrency/countdown_latch_unittest.cpp
ulib/system/thread_pool_unittest.cpp ulib/system/thread_unittest.cpp
ulib/system/timer_unittest.cpp ulib/system/thread_pool_unittest.cpp
3party/inja/inja_unittest.cpp ulib/system/timer_unittest.cpp
3party/optional/optional_unittest.cpp 3party/inja/inja_unittest.cpp
3party/sqlpp11/sqlpp11_unittest.cpp 3party/optional/optional_unittest.cpp
ulib/utils/defer_unittest.cpp 3party/sqlpp11/sqlpp11_unittest.cpp
) ulib/utils/defer_unittest.cpp
target_link_libraries(ulib_test PRIVATE ulib/status_or_unittest.cpp)
ulib target_link_libraries(ulib_test PRIVATE ulib gtest gtest_main)
gtest
gtest_main)
add_test(NAME ulib_test COMMAND ulib_test) add_test(NAME ulib_test COMMAND ulib_test)

View File

@ -0,0 +1,11 @@
#include <gtest/gtest.h>
#include <ulib/status_or.h>
TEST(StatusOr, EmptyValue)
{
ulib::StatusOr<int> s;
EXPECT_FALSE(s.ok());
s = 1;
EXPECT_TRUE(s.ok());
EXPECT_EQ(s.value(), 1);
}