feat add status and status_or #4
@ -1,6 +1,9 @@
|
||||
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_REQUIRED ON)
|
||||
set(CMAKE_C_EXTENSIONS OFF)
|
||||
@ -23,14 +26,16 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
if(ULIB_SHARED_LIB)
|
||||
add_library(${PROJECT_NAME} SHARED "")
|
||||
else()
|
||||
add_library(${PROJECT_NAME} STATIC ""
|
||||
src/ulib/system/timer.cpp
|
||||
add_library(${PROJECT_NAME} STATIC "" src/ulib/system/timer.cpp
|
||||
src/ulib/system/timer.h)
|
||||
endif()
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
3party/mongoose/mongoose.c
|
||||
target_sources(
|
||||
${PROJECT_NAME}
|
||||
PRIVATE 3party/mongoose/mongoose.c
|
||||
src/ulib/base/location.h
|
||||
src/ulib/base/location.cpp
|
||||
src/ulib/status.h
|
||||
src/ulib/status.cpp
|
||||
src/ulib/concorrency/barrier.cpp
|
||||
src/ulib/concorrency/barrier.h
|
||||
src/ulib/concorrency/mutex.cpp
|
||||
@ -50,13 +55,12 @@ target_sources(${PROJECT_NAME} PRIVATE
|
||||
src/ulib/system/thread_pool.h
|
||||
src/ulib/system/thread_pool.cpp
|
||||
src/ulib/system/timer.h
|
||||
src/ulib/system/timer.cpp
|
||||
)
|
||||
src/ulib/system/timer.cpp)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
|
||||
## CMP0063
|
||||
# CMP0063
|
||||
if(POLICY CMP0063)
|
||||
cmake_policy(SET CMP0063 NEW)
|
||||
endif()
|
||||
@ -64,9 +68,15 @@ if(POLICY CMP0048)
|
||||
cmake_policy(SET CMP0048 NEW)
|
||||
endif()
|
||||
|
||||
set(FMT_USE_CPP11 OFF 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)
|
||||
set(FMT_USE_CPP11
|
||||
OFF
|
||||
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/sqlpp11)
|
||||
|
||||
@ -77,24 +87,22 @@ set(BUILD_SHARED_LIBS OFF)
|
||||
set(BUILD_OBJECT_LIBS OFF)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/3party/jsoncpp)
|
||||
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
src/ulib/empty.cpp
|
||||
src/ulib/log/logger.cpp
|
||||
src/ulib/log/log.cpp
|
||||
src/ulib/log/level.cpp
|
||||
)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC fmt::fmt jsoncpp_static sqlpp11::sqlpp11)
|
||||
target_sources(
|
||||
${PROJECT_NAME} PRIVATE src/ulib/empty.cpp src/ulib/log/logger.cpp
|
||||
src/ulib/log/log.cpp src/ulib/log/level.cpp)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC fmt::fmt jsoncpp_static
|
||||
sqlpp11::sqlpp11)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE ULIB_LIBRARY_IMPL)
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
3party/inja
|
||||
target_include_directories(
|
||||
${PROJECT_NAME}
|
||||
PUBLIC 3party/inja
|
||||
3party/mongoose
|
||||
3party/nlohmann
|
||||
3party/nonstd
|
||||
3party/sigslot
|
||||
3party/rxcpp/Rx/v2/src
|
||||
3party/rxcpp/Ix/CPP/src
|
||||
src
|
||||
)
|
||||
src)
|
||||
|
||||
install(TARGETS ${PROJECT_NAME} DESTINATION lib)
|
||||
|
||||
|
253
src/ulib/status.cpp
Normal file
253
src/ulib/status.cpp
Normal 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
401
src/ulib/status.h
Normal 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
355
src/ulib/status_or.h
Normal 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
|
@ -3,7 +3,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
||||
add_executable(ulib_test
|
||||
add_executable(
|
||||
ulib_test
|
||||
ulib/base/types_unittest.cpp
|
||||
ulib/log/log_unittest.cpp
|
||||
ulib/concorrency/mutex_unittest.cpp
|
||||
@ -16,10 +17,7 @@ add_executable(ulib_test
|
||||
3party/optional/optional_unittest.cpp
|
||||
3party/sqlpp11/sqlpp11_unittest.cpp
|
||||
ulib/utils/defer_unittest.cpp
|
||||
)
|
||||
target_link_libraries(ulib_test PRIVATE
|
||||
ulib
|
||||
gtest
|
||||
gtest_main)
|
||||
ulib/status_or_unittest.cpp)
|
||||
target_link_libraries(ulib_test PRIVATE ulib gtest gtest_main)
|
||||
|
||||
add_test(NAME ulib_test COMMAND ulib_test)
|
||||
|
11
tests/ulib/status_or_unittest.cpp
Normal file
11
tests/ulib/status_or_unittest.cpp
Normal 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user