diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt index 5d71f57590..f0aecf566b 100644 --- a/libs/utils/CMakeLists.txt +++ b/libs/utils/CMakeLists.txt @@ -40,6 +40,7 @@ set(DIST_HDRS ${PUBLIC_HDR_DIR}/${TARGET}/SingleInstanceComponentManager.h ${PUBLIC_HDR_DIR}/${TARGET}/Slice.h ${PUBLIC_HDR_DIR}/${TARGET}/StaticString.h + ${PUBLIC_HDR_DIR}/${TARGET}/Status.h ${PUBLIC_HDR_DIR}/${TARGET}/StructureOfArrays.h ${PUBLIC_HDR_DIR}/${TARGET}/Systrace.h ${PUBLIC_HDR_DIR}/${TARGET}/sstream.h @@ -79,6 +80,7 @@ set(SRCS src/sstream.cpp src/string.cpp src/ThreadUtils.cpp + src/Status.cpp ) if (WIN32) @@ -179,6 +181,7 @@ set(TEST_SRCS test/test_utils_main.cpp test/test_Zip2Iterator.cpp test/test_BinaryTreeArray.cpp + test/test_Status.cpp ) if (WEBGL_PTHREADS) diff --git a/libs/utils/include/utils/Status.h b/libs/utils/include/utils/Status.h new file mode 100644 index 0000000000..5b462e5ad8 --- /dev/null +++ b/libs/utils/include/utils/Status.h @@ -0,0 +1,141 @@ +/* +* Copyright (C) 2025 The Android Open Source Project +* +* 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 TNT_UTILS_STATUS_H +#define TNT_UTILS_STATUS_H + +#include +#include +#include +#include + +namespace utils { + +/** + * A code indicating the success or failure of an operation. + */ +enum class StatusCode { + /** The operation completed successfully. */ + OK, + /** The caller provided invalid arguments in the request. */ + INVALID_ARGUMENT, + /** Internal error was occurred while processing the request. */ + INTERNAL, +}; + +/** + * Returns the StatusCode to indicate whether the request was successful. + * If successful, it returns OK with no error message, if not it returns + * other codes with an optional error message. + */ +class UTILS_PUBLIC Status { +public: + /** + * Creates a new Status with a StatusCode of OK. + */ + Status() : mStatusCode(StatusCode::OK) {} + + /** + * Creates a new Status with the given status code and error message. + * + * @param statusCode The status code to use. + * @param errorMessage An optional error message. + */ + Status(StatusCode statusCode, std::string_view errorMessage) : + mStatusCode(statusCode), + mErrorMessage(errorMessage.data(), errorMessage.length()) {} + + Status(const Status& other) = default; + + Status(Status&& other) noexcept = default; + + ~Status() = default; + + Status& operator=(const Status& other) = default; + Status& operator=(Status&& other) noexcept = default; + + bool operator==(const Status& other) const { + return mStatusCode == other.mStatusCode && mErrorMessage == other.mErrorMessage; + } + + bool operator!=(const Status& other) const { + return !(*this == other); + } + + /** + * Returns true if the status is OK. + * @return true if the operation was successful, false otherwise. + */ + bool isOk() const { + return mStatusCode == StatusCode::OK; + } + + /** + * Returns the StatusCode for this Status. + * @return the StatusCode for this Status. + */ + StatusCode getCode() const { + return mStatusCode; + } + + /** + * Returns the error message for this Status. + * @return The error message string. Will be empty if the status is OK. + */ + std::string_view getErrorMessage() const; + + /** + * Convenient factory functions for creating Status objects. + * Example usage: `return utils::Status::internal("internal error");` + */ + + /** + * Creates a success Status with a StatusCode of OK. + * @return a success Status with a StatusCode of OK + */ + static Status ok() { + return {}; + } + + /** + * Creates an error Status with an INTERNAL status code. + * @param message The error message to include. + * @return an error Status with an INTERNAL status code. + */ + static Status internal(std::string_view message) { + return {StatusCode::INTERNAL, message}; + } + + /** + * Creates an error Status with an INVALID_ARGUMENT status code. + * @param message The error message to include. + * @return an error Status with an INVALID_ARGUMENT status code. + */ + static Status invalidArgument(std::string_view message) { + return {StatusCode::INVALID_ARGUMENT, message}; + } + + friend std::ostream& operator<<(std::ostream& os, const Status& status); + +private: + StatusCode mStatusCode; + // Reason for the error if exists. + utils::CString mErrorMessage; +}; + +} // namespace utils + +#endif // TNT_UTILS_STATUS_H diff --git a/libs/utils/src/Status.cpp b/libs/utils/src/Status.cpp new file mode 100644 index 0000000000..db35db996e --- /dev/null +++ b/libs/utils/src/Status.cpp @@ -0,0 +1,37 @@ +/* +* Copyright (C) 2025 The Android Open Source Project +* +* 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. + */ +#include + +namespace utils { +std::string_view Status::getErrorMessage() const { + const char* ptr = mErrorMessage.c_str(); + return (ptr != nullptr) ? ptr : ""; +} + +std::ostream& operator<<(std::ostream& os, const Status& status) { + os << "Status: "; + switch (status.getCode()) { + case StatusCode::OK: os << "Ok"; + break; + case StatusCode::INVALID_ARGUMENT: os << "Invalid argument"; + break; + case StatusCode::INTERNAL: os << "Internal error"; + break; + } + os << ", error message: " << status.getErrorMessage(); + return os; +} +} // namespace utils diff --git a/libs/utils/test/test_Status.cpp b/libs/utils/test/test_Status.cpp new file mode 100644 index 0000000000..68628c9018 --- /dev/null +++ b/libs/utils/test/test_Status.cpp @@ -0,0 +1,127 @@ +/* +* Copyright (C) 2025 The Android Open Source Project +* +* 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. + */ +#include + +#include + +using namespace utils; + +TEST(StatusTest, DefaultConstructorOkStatus) { + Status actual; + Status expected(StatusCode::OK, ""); + EXPECT_EQ(actual, expected); +} + +TEST(StatusTest, Constructor) { + std::string_view errorMessage = "invalid"; + Status status(StatusCode::INVALID_ARGUMENT, errorMessage); + EXPECT_EQ(status.getCode(), StatusCode::INVALID_ARGUMENT); + EXPECT_EQ(status.getErrorMessage(), errorMessage); +} + +TEST(StatusTest, CopyOperator) { + Status status1 = Status::ok(); + EXPECT_EQ(status1.getCode(), StatusCode::OK); + EXPECT_EQ(status1.getErrorMessage(), ""); + + Status status2(StatusCode::INTERNAL, "internal error"); + status1 = status2; + + EXPECT_EQ(status1, status2); +} + +TEST(StatusTest, CopyConstructor) { + Status original = Status::internal("internal error"); + Status copy(original); + EXPECT_EQ(original, copy); +} + +TEST(StatusTest, MoveOperator) { + Status status; + EXPECT_EQ(status.getCode(), StatusCode::OK); + EXPECT_EQ(status.getErrorMessage(), ""); + + std::string_view errorMessage = "internal error"; + Status another(StatusCode::INTERNAL, errorMessage); + status = std::move(another); + + EXPECT_EQ(status.getCode(), StatusCode::INTERNAL); + EXPECT_EQ(status.getErrorMessage(), errorMessage); +} + +TEST(StatusTest, MoveConstructor) { + std::string_view errorMessage = "internal error"; + Status original = Status::internal(errorMessage); + Status moved(std::move(original)); + + EXPECT_EQ(moved.getCode(), StatusCode::INTERNAL); + EXPECT_EQ(moved.getErrorMessage(), errorMessage); +} + +TEST(StatusTest, Equality) { + Status status1(StatusCode::INTERNAL, "internal error"); + Status status2(StatusCode::INTERNAL, "internal error"); + + EXPECT_EQ(status1, status2); +} + +TEST(StatusTest, InEqualityWithDifferentStatusCode) { + Status status1(StatusCode::INTERNAL, "internal error"); + Status status2(StatusCode::INVALID_ARGUMENT, "invalid argument error"); + + EXPECT_NE(status1, status2); +} + +TEST(StatusTest, InEqualityWithDifferentMessage) { + Status status1(StatusCode::INTERNAL, "internal error 1"); + Status status2(StatusCode::INTERNAL, "invalid error 2"); + + EXPECT_NE(status1, status2); +} + +TEST(StatusTest, StaticOk) { + Status expected(StatusCode::OK, ""); + EXPECT_EQ(Status::ok(), expected); +} + +TEST(StatusTest, StaticInvalidArgumentError) { + std::string_view errorMessage = "invalid argument"; + Status expected(StatusCode::INVALID_ARGUMENT, errorMessage); + EXPECT_EQ(Status::invalidArgument(errorMessage), expected); +} + +TEST(StatusTest, StaticInternalError) { + std::string_view errorMessage = "internal error"; + Status expected(StatusCode::INTERNAL, errorMessage); + EXPECT_EQ(Status::internal(errorMessage), expected); +} + +TEST(StatusTest, IsOk) { + Status status = Status::ok(); + EXPECT_TRUE(status.isOk()); + + status = Status::invalidArgument("error"); + EXPECT_FALSE(status.isOk()); +} + +TEST(StatusTest, SelfAssignment) { + Status status = Status::internal("error"); + Status original_copy = status; + + status = status; + + EXPECT_EQ(status, original_copy); +}