This is an automated email from the ASF dual-hosted git repository.
tison pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/incubator-kvrocks.git
The following commit(s) were added to refs/heads/unstable by this push:
new 4ba6b10 Add `StatusOr` for error handling in modern C++ style (#768)
4ba6b10 is described below
commit 4ba6b100ca62044cae9426f02c9e358b8f56841a
Author: Twice <[email protected]>
AuthorDate: Tue Aug 16 22:37:06 2022 +0800
Add `StatusOr` for error handling in modern C++ style (#768)
---
src/redis_cmd.cc | 2 +-
src/redis_connection.cc | 2 +-
src/status.h | 211 ++++++++++++++++++++++++++++++++++++++++---
tests/cppunit/status_test.cc | 146 ++++++++++++++++++++++++++++++
4 files changed, 348 insertions(+), 13 deletions(-)
diff --git a/src/redis_cmd.cc b/src/redis_cmd.cc
index 7437635..6538a98 100644
--- a/src/redis_cmd.cc
+++ b/src/redis_cmd.cc
@@ -118,7 +118,7 @@ class CommandNamespace : public Commander {
} else {
std::string token;
auto s = config->GetNamespace(args_[2], &token);
- if (s.IsNotFound()) {
+ if (s.Is<Status::NotFound>()) {
*output = Redis::NilString();
} else {
*output = Redis::BulkString(token);
diff --git a/src/redis_connection.cc b/src/redis_connection.cc
index 67a81d4..35a276a 100644
--- a/src/redis_connection.cc
+++ b/src/redis_connection.cc
@@ -429,7 +429,7 @@ void Connection::ExecuteCommands(std::deque<CommandTokens>
*to_process_cmds) {
// Break the execution loop when occurring the blocking command like BLPOP
or BRPOP,
// it will suspend the connection and wait for the wakeup signal.
- if (s.IsBlockingCommand()) {
+ if (s.Is<Status::BlockingCmd>()) {
break;
}
// Reply for MULTI
diff --git a/src/status.h b/src/status.h
index 21ae3c4..5ec3f5f 100644
--- a/src/status.h
+++ b/src/status.h
@@ -21,12 +21,17 @@
#pragma once
#include <string>
+#include <type_traits>
#include <utility>
+#include <memory>
+#include <algorithm>
+#include <tuple>
+#include <glog/logging.h>
class Status {
public:
- enum Code {
- cOK,
+ enum Code : unsigned char {
+ cOK = 0,
NotOK,
NotFound,
@@ -61,20 +66,204 @@ class Status {
};
Status() : Status(cOK) {}
- explicit Status(Code code, std::string msg = {}) : code_(code),
msg_(std::move(msg)) {}
- bool IsOK() { return code_ == cOK; }
- bool IsNotFound() { return code_ == NotFound; }
- bool IsImorting() { return code_ == SlotImport; }
- bool IsBlockingCommand() { return code_ == BlockingCmd; }
- std::string Msg() {
- if (IsOK()) {
- return "ok";
- }
+ explicit Status(Code code, std::string msg = {})
+ : code_(code), msg_(std::move(msg)) {}
+
+ template <Code code>
+ bool Is() const { return code_ == code; }
+
+ bool IsOK() const { return Is<cOK>(); }
+ operator bool() const { return IsOK(); }
+
+ Code GetCode() const {
+ return code_;
+ }
+
+ std::string Msg() const& {
+ if (*this) return ok_msg();
return msg_;
}
+
+ std::string Msg() && {
+ if (*this) return ok_msg();
+ return std::move(msg_);
+ }
+
static Status OK() { return {}; }
private:
Code code_;
std::string msg_;
+
+ static constexpr const char* ok_msg() {
+ return "ok";
+ }
+
+ template <typename T>
+ friend struct StatusOr;
+};
+
+template <typename ...Ts>
+using first_element = typename std::tuple_element<0, std::tuple<Ts...>>::type;
+
+template <typename T>
+using remove_cvref_t = typename std::remove_cv<typename
std::remove_reference<T>::type>::type;
+
+template <typename T>
+struct StatusOr;
+
+template <typename T>
+struct IsStatusOr : std::integral_constant<bool, false> {};
+
+template <typename T>
+struct IsStatusOr<StatusOr<T>> : std::integral_constant<bool, true> {};
+
+template <typename T>
+struct StatusOr {
+ static_assert(!std::is_same<T, Status>::value, "value_type cannot be
Status");
+ static_assert(!std::is_same<T, Status::Code>::value, "value_type cannot be
Status::Code");
+ static_assert(!IsStatusOr<T>::value, "value_type cannot be StatusOr");
+ static_assert(!std::is_reference<T>::value, "value_type cannot be
reference");
+
+ using value_type = T;
+
+ // we use std::unique_ptr to make the error part as small as enough
+ using error_type = std::unique_ptr<std::string>;
+
+ using Code = Status::Code;
+
+ explicit StatusOr(Status s) : code_(s.code_) {
+ CHECK(!s);
+ new(value_or_error_) error_type(new std::string(std::move(s.msg_)));
+ }
+
+ StatusOr(Code code, std::string msg = {}) : code_(code) { // NOLINT
+ CHECK(code != Code::cOK);
+ new(value_or_error_) error_type(new std::string(std::move(msg)));
+ }
+
+ template <typename ...Ts,
+ typename std::enable_if<
+ (sizeof...(Ts) > 0 &&
+ !std::is_same<Status, remove_cvref_t<first_element<Ts...>>>::value &&
+ !std::is_same<Code, remove_cvref_t<first_element<Ts...>>>::value &&
+ !std::is_same<value_type, remove_cvref_t<first_element<Ts...>>>::value
&&
+ !std::is_same<StatusOr, remove_cvref_t<first_element<Ts...>>>::value
+ ), int>::type = 0> // NOLINT
+ explicit StatusOr(Ts && ... args) : code_(Code::cOK) {
+ new(value_or_error_) value_type(std::forward<Ts>(args)...);
+ }
+
+ StatusOr(T&& value) : code_(Code::cOK) { // NOLINT
+ new(value_or_error_) value_type(std::move(value));
+ }
+
+ StatusOr(const T& value) : code_(Code::cOK) { // NOLINT
+ new(value_or_error_) value_type(value);
+ }
+
+ StatusOr(const StatusOr&) = delete;
+ StatusOr(StatusOr&& other) : code_(other.code_) {
+ if (code_ == Code::cOK) {
+ new(value_or_error_) value_type(std::move(other.getValue()));
+ } else {
+ new(value_or_error_) error_type(std::move(other.getError()));
+ }
+ }
+
+ Status& operator=(const Status&) = delete;
+
+ template <Code code>
+ bool Is() const { return code_ == code; }
+
+ bool IsOK() const { return Is<Code::cOK>(); }
+ operator bool() const { return IsOK(); }
+
+ Status ToStatus() const& {
+ if (*this) return Status::OK();
+ return Status(code_, *getError());
+ }
+
+ Status ToStatus() && {
+ if (*this) return Status::OK();
+ return Status(code_, std::move(*getError()));
+ }
+
+ Code GetCode() const {
+ return code_;
+ }
+
+ value_type& GetValue() & {
+ CHECK(*this);
+ return getValue();
+ }
+
+ value_type&& GetValue() && {
+ CHECK(*this);
+ return std::move(getValue());
+ }
+
+ const value_type& GetValue() const& {
+ CHECK(*this);
+ return getValue();
+ }
+
+ value_type& operator*() & {
+ return GetValue();
+ }
+
+ value_type&& operator*() && {
+ return std::move(GetValue());
+ }
+
+ const value_type& operator*() const& {
+ return GetValue();
+ }
+
+ value_type* operator->() {
+ return &GetValue();
+ }
+
+ const value_type* operator->() const {
+ return &GetValue();
+ }
+
+ std::string Msg() const& {
+ if (*this) return Status::ok_msg();
+ return *getError();
+ }
+
+ std::string Msg() && {
+ if (*this) return Status::ok_msg();
+ return std::move(*getError());
+ }
+
+ ~StatusOr() {
+ if (*this) {
+ getValue().~value_type();
+ } else {
+ getError().~error_type();
+ }
+ }
+
+ private:
+ Status::Code code_;
+ alignas(value_type) alignas(error_type) unsigned char value_or_error_
+ [sizeof(value_type) < sizeof(error_type) ? sizeof(error_type) :
sizeof(value_type)];
+
+ value_type& getValue() {
+ return *reinterpret_cast<value_type*>(value_or_error_);
+ }
+
+ const value_type& getValue() const {
+ return *reinterpret_cast<const value_type*>(value_or_error_);
+ }
+
+ error_type& getError() {
+ return *reinterpret_cast<error_type*>(value_or_error_);
+ }
+
+ const error_type& getError() const {
+ return *reinterpret_cast<const error_type*>(value_or_error_);
+ }
};
diff --git a/tests/cppunit/status_test.cc b/tests/cppunit/status_test.cc
new file mode 100644
index 0000000..dac3683
--- /dev/null
+++ b/tests/cppunit/status_test.cc
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <status.h>
+
+TEST(StatusOr, Scalar) {
+ auto f = [](int x) -> StatusOr<int> {
+ if (x > 10) {
+ return {Status::NotOK, "x large than 10"};
+ }
+
+ return 2 * x + 5;
+ };
+
+ ASSERT_EQ(*f(1), 7);
+ ASSERT_EQ(*f(5), 15);
+ ASSERT_EQ(f(7).GetValue(), 19);
+ ASSERT_EQ(f(7).GetCode(), Status::cOK);
+ ASSERT_EQ(f(7).Msg(), "ok");
+ ASSERT_TRUE(f(6));
+ ASSERT_EQ(f(11).GetCode(), Status::NotOK);
+ ASSERT_EQ(f(11).Msg(), "x large than 10");
+ ASSERT_FALSE(f(12));
+
+ auto x = f(5);
+ ASSERT_EQ(*x, 15);
+ ASSERT_EQ(x.Msg(), "ok");
+ ASSERT_EQ(x.GetValue(), 15);
+ ASSERT_EQ(x.GetCode(), Status::cOK);
+
+ auto y = f(11);
+ ASSERT_EQ(y.Msg(), "x large than 10");
+ ASSERT_EQ(y.GetCode(), Status::NotOK);
+
+ auto g = [f](int y) -> StatusOr<int> {
+ if (y > 5 && y < 15) {
+ return {Status::NotOK, "y large than 5"};
+ }
+
+ auto res = f(y);
+ if (!res) return res;
+
+ return *res * 10;
+ };
+
+ ASSERT_EQ(*g(1), 70);
+ ASSERT_EQ(*g(5), 150);
+ ASSERT_EQ(g(1).GetValue(), 70);
+ ASSERT_EQ(g(1).GetCode(), Status::cOK);
+ ASSERT_EQ(g(1).Msg(), "ok");
+ ASSERT_EQ(g(6).GetCode(), Status::NotOK);
+ ASSERT_EQ(g(6).Msg(), "y large than 5");
+ ASSERT_EQ(g(20).GetCode(), Status::NotOK);
+ ASSERT_EQ(g(20).Msg(), "x large than 10");
+ ASSERT_EQ(g(11).GetCode(), Status::NotOK);
+ ASSERT_EQ(g(11).Msg(), "y large than 5");
+}
+
+TEST(StatusOr, String) {
+ auto f = [](std::string x) -> StatusOr<std::string> {
+ if (x.size() > 10) {
+ return {Status::NotOK, "string too long"};
+ }
+
+ return x + " hello";
+ };
+
+ auto g = [f](std::string x) -> StatusOr<std::string> {
+ if (x.size() < 5) {
+ return {Status::NotOK, "string too short"};
+ }
+
+ auto res = f(x);
+ if (!res) return res;
+
+ return "hi " + *res;
+ };
+
+ ASSERT_TRUE(f("1"));
+ ASSERT_FALSE(f("12345678901"));
+ ASSERT_FALSE(g("1"));
+
+ ASSERT_EQ(*f("twice"), "twice hello");
+ ASSERT_EQ(*g("twice"), "hi twice hello");
+ ASSERT_EQ(g("shrt").GetCode(), Status::NotOK);
+ ASSERT_EQ(g("shrt").Msg(), "string too short");
+ ASSERT_EQ(g("loooooooooooog").GetCode(), Status::NotOK);
+ ASSERT_EQ(g("loooooooooooog").Msg(), "string too long");
+
+ ASSERT_EQ(g("twice").ToStatus().GetCode(), Status::cOK);
+ ASSERT_EQ(g("").ToStatus().GetCode(), Status::NotOK);
+
+ auto x = g("twice");
+ ASSERT_EQ(x.ToStatus().GetCode(), Status::cOK);
+ auto y = g("");
+ ASSERT_EQ(y.ToStatus().GetCode(), Status::NotOK);
+}
+
+TEST(StatusOr, SharedPtr) {
+ struct A {
+ A(int *x) : x(x) { *x = 233; }
+ ~A() { *x = 0; }
+
+ int *x;
+ };
+
+ int val = 0;
+
+ {
+ StatusOr<std::shared_ptr<A>> x(new A(&val));
+
+ ASSERT_EQ(val, 233);
+ ASSERT_EQ(x->use_count(), 1);
+
+ {
+ StatusOr<std::shared_ptr<A>> y(*x);
+ ASSERT_EQ(val, 233);
+ ASSERT_EQ(x->use_count(), 2);
+ }
+
+ ASSERT_EQ(x->use_count(), 1);
+ }
+
+ ASSERT_EQ(val, 0);
+
+}