From afc102e90a948732941e825a21466267b943cd89 Mon Sep 17 00:00:00 2001 From: Tom Ritchford Date: Tue, 30 Sep 2014 20:11:23 -0400 Subject: [PATCH] New class RPC::Status enforces JSON-RPC 2.0 error format. * Relevant issues: * RIPD-92 * RIPD-97 * RIPD-98 * RIPD-439 --- Builds/VisualStudio2013/RippleD.vcxproj | 9 + .../VisualStudio2013/RippleD.vcxproj.filters | 12 + src/ripple/common/jsonrpc_fields.h | 1 + src/ripple/rpc/Status.h | 121 ++++++++++ src/ripple/rpc/impl/Status.cpp | 77 +++++++ src/ripple/rpc/impl/Status_test.cpp | 214 ++++++++++++++++++ src/ripple/unity/rpcx.cpp | 2 + 7 files changed, 436 insertions(+) create mode 100644 src/ripple/rpc/Status.h create mode 100644 src/ripple/rpc/impl/Status.cpp create mode 100644 src/ripple/rpc/impl/Status_test.cpp diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj index 0145acd94f..e50b145671 100644 --- a/Builds/VisualStudio2013/RippleD.vcxproj +++ b/Builds/VisualStudio2013/RippleD.vcxproj @@ -3185,6 +3185,11 @@ True + + True + + + True @@ -3207,12 +3212,16 @@ + + + + diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters index 73e1283677..0fc74ac9e8 100644 --- a/Builds/VisualStudio2013/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters @@ -4371,6 +4371,12 @@ ripple\rpc\impl + + ripple\rpc\impl + + + ripple\rpc\impl + ripple\rpc\impl @@ -4398,6 +4404,9 @@ ripple\rpc + + ripple\rpc + ripple\rpc @@ -4407,6 +4416,9 @@ ripple\rpc + + ripple\rpc + ripple\sitefiles\api diff --git a/src/ripple/common/jsonrpc_fields.h b/src/ripple/common/jsonrpc_fields.h index 6329997317..de640bacb9 100644 --- a/src/ripple/common/jsonrpc_fields.h +++ b/src/ripple/common/jsonrpc_fields.h @@ -64,6 +64,7 @@ JSS ( consensus ); JSS ( converge_time ); JSS ( converge_time_s ); JSS ( currency ); +JSS ( data ); JSS ( date ); JSS ( engine_result ); JSS ( engine_result_code ); diff --git a/src/ripple/rpc/Status.h b/src/ripple/rpc/Status.h new file mode 100644 index 0000000000..d16452f615 --- /dev/null +++ b/src/ripple/rpc/Status.h @@ -0,0 +1,121 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_STATUS_H +#define RIPPLE_STATUS_H + +#include +#include + +namespace ripple { +namespace RPC { +namespace New { + +/** Status represents the results of an operation that might fail. + + It wraps the legacy codes TER and error_code_i, providing both a uniform + interface and a way to attach additional information to existing status + returns. + + A Status can also be used to fill a Json::Value with a JSON-RPC 2.0 + error response: see http://www.jsonrpc.org/specification#error_object + */ +struct Status +{ +public: + enum class Type {none, TER, error_code_i}; + using Code = int; + using Strings = std::vector ; + + static const Code OK = 0; + + Status () = default; + + Status (Code code, Strings d = {}) + : type_ (Type::none), code_ (code), messages_ (std::move (d)) + { + } + + Status (TER ter, Strings d = {}) + : type_ (Type::TER), code_ (ter), messages_ (std::move (d)) + { + } + + Status (error_code_i e, Strings d = {}) + : type_ (Type::error_code_i), code_ (e), messages_ (std::move (d)) + { + } + + /* Returns a representation of the integer status Code as a string. + If the Status is OK, the result is an empty string. + */ + std::string codeString () const; + + /** Fill a Json::Value. If the Status is OK, fillJson has no effect. */ + void fillJson(Json::Value&); + + /** Returns true if the Status is *not* OK. */ + operator bool() const + { + return code_ != OK; + } + + /** Returns true if the Status is OK. */ + bool operator !() const + { + return ! bool (*this); + } + + /** Returns the Status as a TER. + This may only be called if type() == Type::TER. */ + TER toTER () const + { + assert (type_ == Type::TER); + return TER (code_); + } + + /** Returns the Status as an error_code_i. + This may only be called if type() == Type::error_code_i. */ + error_code_i toErrorCode() const + { + assert (type_ == Type::error_code_i); + return error_code_i (code_); + } + + Strings const& messages() const + { + return messages_; + } + + Type type() const + { + return type_; + } + +private: + Type type_ = Type::none; + Code code_ = OK; + Strings messages_; +}; + +} // namespace New +} // namespace RPC +} // ripple + +#endif diff --git a/src/ripple/rpc/impl/Status.cpp b/src/ripple/rpc/impl/Status.cpp new file mode 100644 index 0000000000..a2e1ef971b --- /dev/null +++ b/src/ripple/rpc/impl/Status.cpp @@ -0,0 +1,77 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +namespace ripple { +namespace RPC { +namespace New { + +std::string Status::codeString () const +{ + if (!*this) + return ""; + + if (type_ == Type::none) + return std::to_string (code_); + + if (type_ == Status::Type::TER) + { + std::string s1, s2; + + auto success = transResultInfo (toTER (), s1, s2); + assert (success); + (void) success; + + return s1 + ": " + s2; + } + + if (type_ == Status::Type::error_code_i) + { + auto info = get_error_info (toErrorCode ()); + return info.token + ": " + info.message; + } + + assert (false); + return ""; +} + +void Status::fillJson (Json::Value& value) +{ + static const std::string separator = ": "; + + if (!*this) + return; + + auto& error = value[jss::error]; + error[jss::code] = code_; + error[jss::message] = codeString (); + + // Are there any more messages? + if (!messages_.empty ()) + { + auto& messages = error[jss::data]; + for (auto& i: messages_) + messages.append (i); + } +} + +} // namespace New +} // namespace RPC +} // ripple diff --git a/src/ripple/rpc/impl/Status_test.cpp b/src/ripple/rpc/impl/Status_test.cpp new file mode 100644 index 0000000000..e7cfae7795 --- /dev/null +++ b/src/ripple/rpc/impl/Status_test.cpp @@ -0,0 +1,214 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include + +namespace ripple { +namespace RPC { +namespace New { + +class codeString_test : public beast::unit_test::suite +{ +private: + template + std::string codeString (Type t) + { + return Status (t).codeString(); + } + + void test_OK () + { + testcase ("OK"); + { + auto s = codeString (Status ()); + expect (s.empty(), "String for OK status"); + } + + { + auto s = codeString (Status::OK); + expect (s.empty(), "String for OK status"); + } + + { + auto s = codeString (0); + expect (s.empty(), "String for 0 status"); + } + + { + auto s = codeString (tesSUCCESS); + expect (s.empty(), "String for tesSUCCESS"); + } + + { + auto s = codeString (rpcSUCCESS); + expect (s.empty(), "String for rpcSUCCESS"); + } + } + + void test_error () + { + testcase ("error"); + { + auto s = codeString (23); + expect (s == "23", s); + } + + { + auto s = codeString (temBAD_AMOUNT); + expect (s == "temBAD_AMOUNT: Can only send positive amounts.", s); + } + + { + auto s = codeString (rpcBAD_SYNTAX); + expect (s == "badSyntax: Syntax error.", s); + } + } + +public: + void run() + { + test_OK (); + test_error (); + } +}; + +BEAST_DEFINE_TESTSUITE (codeString, Status, RPC); + +class fillJson_test : public beast::unit_test::suite +{ +private: + Json::Value value_; + + template + void fillJson (Type t) + { + value_.clear (); + Status (t).fillJson (value_); + } + + void test_OK () + { + testcase ("OK"); + fillJson (Status ()); + expect (value_.empty(), "Value for empty status"); + + fillJson (0); + expect (value_.empty(), "Value for 0 status"); + + fillJson (Status::OK); + expect (value_.empty(), "Value for OK status"); + + fillJson (tesSUCCESS); + expect (value_.empty(), "Value for tesSUCCESS"); + + fillJson (rpcSUCCESS); + expect (value_.empty(), "Value for rpcSUCCESS"); + } + + template + void expectFill ( + std::string const& label, + Type status, + Status::Strings messages, + std::string const& message) + { + value_.clear (); + fillJson (Status (status, messages)); + + auto prefix = label + ": "; + expect (!value_.empty(), prefix + "No value"); + + auto error = value_[jss::error]; + expect (!error.empty(), prefix + "No error."); + + auto code = error[jss::code].asInt(); + expect (status == code, prefix + "Wrong status " + + std::to_string (code) + " != " + std::to_string (status)); + + auto m = error[jss::message].asString (); + expect (m == message, m + " != " + message); + + auto d = error[jss::data]; + size_t s1 = d.size(), s2 = messages.size(); + expect (s1 == s2, prefix + "Data sizes differ " + + std::to_string (s1) + " != " + std::to_string (s2)); + for (auto i = 0; i < std::min (s1, s2); ++i) + { + auto ds = d[i].asString(); + expect (ds == messages[i], prefix + ds + " != " + messages[i]); + } + } + + void test_error () + { + testcase ("error"); + expectFill ( + "temBAD_AMOUNT", + temBAD_AMOUNT, + {}, + "temBAD_AMOUNT: Can only send positive amounts."); + + expectFill ( + "rpcBAD_SYNTAX", + rpcBAD_SYNTAX, + {"An error.", "Another error."}, + "badSyntax: Syntax error."); + + expectFill ( + "integer message", + 23, + {"Stuff."}, + "23"); + } + + void test_throw () + { + testcase ("throw"); + try + { + throw Status (temBAD_PATH, {"path=sdcdfd"}); + } + catch (Status const& s) + { + expect (s.toTER () == temBAD_PATH, "temBAD_PATH wasn't thrown"); + auto msgs = s.messages (); + expect (msgs.size () == 1, "Wrong number of messages"); + expect (msgs[0] == "path=sdcdfd", msgs[0]); + } + catch (...) + { + expect (false, "Didn't catch a Status"); + } + } + +public: + void run() + { + test_OK (); + test_error (); + test_throw (); + } +}; + +BEAST_DEFINE_TESTSUITE (fillJson, Status, RPC); + +} // namespace New +} // namespace RPC +} // ripple diff --git a/src/ripple/unity/rpcx.cpp b/src/ripple/unity/rpcx.cpp index 0da7722011..52b9dc43d3 100644 --- a/src/ripple/unity/rpcx.cpp +++ b/src/ripple/unity/rpcx.cpp @@ -35,6 +35,8 @@ #include #include #include +#include +#include #include #include