Improved human readable JSON-RPC error messages

This commit is contained in:
Vinnie Falco
2013-12-12 21:17:54 -05:00
parent ef7810bc95
commit 9d07ddeae1
22 changed files with 1682 additions and 839 deletions

View File

@@ -0,0 +1,717 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
namespace ripple {
STParsedJSON::STParsedJSON (std::string const& name, Json::Value const& json)
{
parse (name, json, sfGeneric, 0, object);
}
//------------------------------------------------------------------------------
std::string STParsedJSON::make_name (std::string const& object,
std::string const& field)
{
if (field.empty ())
return object;
return object + "." + field;
}
Json::Value STParsedJSON::not_an_object (std::string const& object,
std::string const& field)
{
return RPC::make_error (rpcINVALID_PARAMS,
"Field '" + make_name (object, field) + "' is not a JSON object.");
}
Json::Value STParsedJSON::unknown_field (std::string const& object,
std::string const& field)
{
return RPC::make_error (rpcINVALID_PARAMS,
"Field '" + make_name (object, field) + "' is unknown.");
}
Json::Value STParsedJSON::out_of_range (std::string const& object,
std::string const& field)
{
return RPC::make_error (rpcINVALID_PARAMS,
"Field '" + make_name (object, field) + "' is out of range.");
}
Json::Value STParsedJSON::bad_type (std::string const& object,
std::string const& field)
{
return RPC::make_error (rpcINVALID_PARAMS,
"Field '" + make_name (object, field) + "' has bad type.");
}
Json::Value STParsedJSON::invalid_data (std::string const& object,
std::string const& field)
{
return RPC::make_error (rpcINVALID_PARAMS,
"Field '" + make_name (object, field) + "' has invalid data.");
}
Json::Value STParsedJSON::array_expected (std::string const& object,
std::string const& field)
{
return RPC::make_error (rpcINVALID_PARAMS,
"Field '" + make_name (object, field) + "' must be a JSON array.");
}
Json::Value STParsedJSON::string_expected (std::string const& object,
std::string const& field)
{
return RPC::make_error (rpcINVALID_PARAMS,
"Field '" + make_name (object, field) + "' must be a string.");
}
Json::Value STParsedJSON::too_deep (std::string const& object,
std::string const& field)
{
return RPC::make_error (rpcINVALID_PARAMS,
"Field '" + make_name (object, field) + "' exceeds nesting depth limit.");
}
Json::Value STParsedJSON::singleton_expected (std::string const& object)
{
return RPC::make_error (rpcINVALID_PARAMS,
"Field '" + object +
"' must be an object with a single key/object value.");
}
//------------------------------------------------------------------------------
bool STParsedJSON::parse (std::string const& json_name,
Json::Value const& json, SField::ref inName, int depth,
std::unique_ptr <STObject>& sub_object)
{
if (! json.isObject ())
{
error = not_an_object (json_name);
return false;
}
SField::ptr name (&inName);
boost::ptr_vector<SerializedType> data;
Json::Value::Members members (json.getMemberNames ());
for (Json::Value::Members::iterator it (members.begin ());
it != members.end (); ++it)
{
std::string const& fieldName = *it;
Json::Value const& value = json [fieldName];
SField::ref field = SField::getField (fieldName);
if (field == sfInvalid)
{
error = unknown_field (json_name, fieldName);
return false;
}
switch (field.fieldType)
{
case STI_UINT8:
try
{
if (value.isString ())
{
// VFALCO TODO wtf?
}
else if (value.isInt ())
{
if (value.asInt () < 0 || value.asInt () > 255)
{
error = out_of_range (json_name, fieldName);
return false;
}
data.push_back (new STUInt8 (field,
range_check_cast <unsigned char> (
value.asInt (), 0, 255)));
}
else if (value.isUInt ())
{
if (value.asUInt () > 255)
{
error = out_of_range (json_name, fieldName);
return false;
}
data.push_back (new STUInt8 (field,
range_check_cast <unsigned char> (
value.asUInt (), 0, 255)));
}
else
{
error = bad_type (json_name, fieldName);
return false;
}
}
catch (...)
{
error = invalid_data (json_name, fieldName);
return false;
}
break;
case STI_UINT16:
try
{
if (value.isString ())
{
std::string strValue = value.asString ();
if (! strValue.empty () &&
((strValue[0] < '0') || (strValue[0] > '9')))
{
if (field == sfTransactionType)
{
TxType const txType (TxFormats::getInstance()->
findTypeByName (strValue));
data.push_back (new STUInt16 (field,
static_cast <uint16> (txType)));
if (*name == sfGeneric)
name = &sfTransaction;
}
else if (field == sfLedgerEntryType)
{
LedgerEntryType const type (LedgerFormats::getInstance()->
findTypeByName (strValue));
data.push_back (new STUInt16 (field,
static_cast <uint16> (type)));
if (*name == sfGeneric)
name = &sfLedgerEntry;
}
else
{
error = invalid_data (json_name, fieldName);
return false;
}
}
else
{
data.push_back (new STUInt16 (field,
lexicalCastThrow <uint16> (strValue)));
}
}
else if (value.isInt ())
{
data.push_back (new STUInt16 (field,
range_check_cast <uint16> (
value.asInt (), 0, 65535)));
}
else if (value.isUInt ())
{
data.push_back (new STUInt16 (field,
range_check_cast <uint16> (
value.asUInt (), 0, 65535)));
}
else
{
error = bad_type (json_name, fieldName);
return false;
}
}
catch (...)
{
error = invalid_data (json_name, fieldName);
return false;
}
break;
case STI_UINT32:
try
{
if (value.isString ())
{
data.push_back (new STUInt32 (field,
lexicalCastThrow <uint32> (value.asString ())));
}
else if (value.isInt ())
{
data.push_back (new STUInt32 (field,
range_check_cast <uint32> (value.asInt (), 0u, 4294967295u)));
}
else if (value.isUInt ())
{
data.push_back (new STUInt32 (field,
static_cast <uint32> (value.asUInt ())));
}
else
{
error = bad_type (json_name, fieldName);
return false;
}
}
catch (...)
{
error = invalid_data (json_name, fieldName);
return false;
}
break;
case STI_UINT64:
try
{
if (value.isString ())
{
data.push_back (new STUInt64 (field,
uintFromHex (value.asString ())));
}
else if (value.isInt ())
{
data.push_back (new STUInt64 (field,
range_check_cast<uint64> (
value.asInt (), 0, 18446744073709551615ull)));
}
else if (value.isUInt ())
{
data.push_back (new STUInt64 (field,
static_cast <uint64> (value.asUInt ())));
}
else
{
error = bad_type (json_name, fieldName);
return false;
}
}
catch (...)
{
error = invalid_data (json_name, fieldName);
return false;
}
break;
case STI_HASH128:
try
{
if (value.isString ())
{
data.push_back (new STHash128 (field, value.asString ()));
}
else
{
error = bad_type (json_name, fieldName);
return false;
}
}
catch (...)
{
error = invalid_data (json_name, fieldName);
return false;
}
break;
case STI_HASH160:
try
{
if (value.isString ())
{
data.push_back (new STHash160 (field, value.asString ()));
}
else
{
error = bad_type (json_name, fieldName);
return false;
}
}
catch (...)
{
error = invalid_data (json_name, fieldName);
return false;
}
break;
case STI_HASH256:
try
{
if (value.isString ())
{
data.push_back (new STHash256 (field, value.asString ()));
}
else
{
error = bad_type (json_name, fieldName);
return false;
}
}
catch (...)
{
error = invalid_data (json_name, fieldName);
return false;
}
break;
case STI_VL:
if (! value.isString ())
{
error = bad_type (json_name, fieldName);
return false;
}
try
{
data.push_back (new STVariableLength (field,
strUnHex (value.asString ())));
}
catch (...)
{
error = invalid_data (json_name, fieldName);
return false;
}
break;
case STI_AMOUNT:
try
{
data.push_back (new STAmount (field, value));
}
catch (...)
{
error = invalid_data (json_name, fieldName);
return false;
}
break;
case STI_VECTOR256:
if (! value.isArray ())
{
error = array_expected (json_name, fieldName);
return false;
}
try
{
data.push_back (new STVector256 (field));
STVector256* tail (dynamic_cast <STVector256*> (&data.back ()));
check_precondition (tail);
for (Json::UInt i = 0; !json.isValidIndex (i); ++i)
{
uint256 s;
s.SetHex (json[i].asString ());
tail->addValue (s);
}
}
catch (...)
{
error = invalid_data (json_name, fieldName);
return false;
}
break;
case STI_PATHSET:
if (!value.isArray ())
{
error = array_expected (json_name, fieldName);
return false;
}
try
{
data.push_back (new STPathSet (field));
STPathSet* tail = dynamic_cast <STPathSet*> (&data.back ());
check_precondition (tail);
for (Json::UInt i = 0; value.isValidIndex (i); ++i)
{
STPath p;
if (!value[i].isArray ())
{
std::stringstream ss;
ss << fieldName << "[" << i << "]";
error = array_expected (json_name, ss.str ());
return false;
}
for (Json::UInt j = 0; value[i].isValidIndex (j); ++j)
{
std::stringstream ss;
ss << fieldName << "[" << i << "][" << j << "]";
std::string const element_name (
json_name + "." + ss.str());
// each element in this path has some combination of account,
// currency, or issuer
Json::Value pathEl = value[i][j];
if (!pathEl.isObject ())
{
error = not_an_object (element_name);
return false;
}
const Json::Value& account = pathEl["account"];
const Json::Value& currency = pathEl["currency"];
const Json::Value& issuer = pathEl["issuer"];
bool hasCurrency = false;
uint160 uAccount, uCurrency, uIssuer;
if (! account.isNull ())
{
// human account id
if (! account.isString ())
{
error = string_expected (element_name, "account");
return false;
}
std::string const strValue (account.asString ());
if (value.size () == 40) // 160-bit hex account value
uAccount.SetHex (strValue);
{
RippleAddress a;
if (! a.setAccountID (strValue))
{
error = invalid_data (element_name, "account");
return false;
}
uAccount = a.getAccountID ();
}
}
if (!currency.isNull ())
{
// human currency
if (!currency.isString ())
{
error = string_expected (element_name, "currency");
return false;
}
hasCurrency = true;
if (currency.asString ().size () == 40)
{
uCurrency.SetHex (currency.asString ());
}
else if (!STAmount::currencyFromString (
uCurrency, currency.asString ()))
{
error = invalid_data (element_name, "currency");
return false;
}
}
if (!issuer.isNull ())
{
// human account id
if (!issuer.isString ())
{
error = string_expected (element_name, "issuer");
return false;
}
if (issuer.asString ().size () == 40)
{
uIssuer.SetHex (issuer.asString ());
}
else
{
RippleAddress a;
if (!a.setAccountID (issuer.asString ()))
{
error = invalid_data (element_name, "issuer");
return false;
}
uIssuer = a.getAccountID ();
}
}
p.addElement (STPathElement (uAccount, uCurrency, uIssuer, hasCurrency));
}
tail->addPath (p);
}
}
catch (...)
{
error = invalid_data (json_name, fieldName);
return false;
}
break;
case STI_ACCOUNT:
{
if (! value.isString ())
{
error = bad_type (json_name, fieldName);
return false;
}
std::string strValue = value.asString ();
try
{
if (value.size () == 40) // 160-bit hex account value
{
uint160 v;
v.SetHex (strValue);
data.push_back (new STAccount (field, v));
}
else
{
// ripple address
RippleAddress a;
if (!a.setAccountID (strValue))
{
error = invalid_data (json_name, fieldName);
return false;
}
data.push_back (new STAccount (field, a.getAccountID ()));
}
}
catch (...)
{
error = invalid_data (json_name, fieldName);
return false;
}
}
break;
case STI_OBJECT:
case STI_TRANSACTION:
case STI_LEDGERENTRY:
case STI_VALIDATION:
if (! value.isObject ())
{
error = not_an_object (json_name, fieldName);
return false;
}
if (depth > 64)
{
error = too_deep (json_name, fieldName);
return false;
}
try
{
std::unique_ptr <STObject> sub_object_;
bool const success (parse (json_name + "." + fieldName,
value, field, depth + 1, sub_object_));
if (! success)
return false;
data.push_back (sub_object_.release ());
}
catch (...)
{
error = invalid_data (json_name, fieldName);
return false;
}
break;
case STI_ARRAY:
if (! value.isArray ())
{
error = array_expected (json_name, fieldName);
return false;
}
try
{
data.push_back (new STArray (field));
STArray* tail = dynamic_cast<STArray*> (&data.back ());
check_precondition (tail);
for (Json::UInt i = 0; value.isValidIndex (i); ++i)
{
bool const isObject (value[i].isObject());
bool const singleKey (isObject
? value [i].size() == 1
: true);
if (!isObject || !singleKey)
{
std::stringstream ss;
ss << json_name << "." << fieldName << "[" << i << "]";
error = singleton_expected (ss.str ());
return false;
}
// TODO: There doesn't seem to be a nice way to get just the
// first/only key in an object without copying all keys into
// a vector
std::string const objectName (value[i].getMemberNames()[0]);;
SField::ref const nameField (SField::getField(objectName));
Json::Value const objectFields (value[i][objectName]);
std::unique_ptr <STObject> sub_object_;
{
std::stringstream ss;
ss << json_name << "." << fieldName <<
"[" << i << "]." << objectName;
bool const success (parse (ss.str (), objectFields,
nameField, depth + 1, sub_object_));
if (! success)
return false;
}
tail->push_back (*sub_object_);
}
}
catch (...)
{
error = invalid_data (json_name, fieldName);
return false;
}
break;
default:
error = bad_type (json_name, fieldName);
return false;
}
}
sub_object.reset (new STObject (*name, data));
return true;
}
}

View File

@@ -0,0 +1,84 @@
//------------------------------------------------------------------------------
/*
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_DATA_STPARSEDJSON_H
#define RIPPLE_DATA_STPARSEDJSON_H
namespace ripple {
/** Holds the serialized result of parsing input JSON.
This does validation and checking on the provided JSON.
*/
class STParsedJSON
{
public:
/** Parses and creates an STParsedJSON object.
The result of the parsing is stored in object and error.
Exceptions:
Does not throw.
@param name The name of the JSON field, used in diagnostics.
@param json The JSON-RPC to parse.
*/
STParsedJSON (std::string const& name,
Json::Value const& json);
/** The STObject if the parse was successful. */
std::unique_ptr <STObject> object;
/** On failure, an appropriate set of error values. */
Json::Value error;
private:
static std::string make_name (std::string const& object,
std::string const& field);
static Json::Value not_an_object (std::string const& object,
std::string const& field = std::string());
static Json::Value unknown_field (std::string const& object,
std::string const& field = std::string());
static Json::Value out_of_range (std::string const& object,
std::string const& field = std::string());
static Json::Value bad_type (std::string const& object,
std::string const& field = std::string());
static Json::Value invalid_data (std::string const& object,
std::string const& field = std::string());
static Json::Value array_expected (std::string const& object,
std::string const& field = std::string());
static Json::Value string_expected (std::string const& object,
std::string const& field = std::string());
static Json::Value too_deep (std::string const& object,
std::string const& field = std::string());
static Json::Value singleton_expected (
std::string const& object);
bool parse (std::string const& json_name, Json::Value const& json,
SField::ref inName, int depth, std::unique_ptr <STObject>& sub_object);
};
}
#endif

View File

@@ -138,7 +138,7 @@ void STObject::set (const SOTemplate& type)
mData.clear ();
mType = &type;
BOOST_FOREACH (const SOElement * elem, type.peek ())
for (SOTemplate::value_type const& elem : type.peek ())
{
if (elem->flags != SOE_REQUIRED)
giveObject (makeNonPresentObject (elem->e_field));
@@ -154,7 +154,7 @@ bool STObject::setType (const SOTemplate& type)
mType = &type;
BOOST_FOREACH (const SOElement * elem, type.peek ())
for (SOTemplate::value_type const& elem : type.peek ())
{
bool match = false;
@@ -208,7 +208,7 @@ bool STObject::isValidForType ()
{
boost::ptr_vector<SerializedType>::iterator it = mData.begin ();
BOOST_FOREACH (const SOElement * elem, mType->peek ())
for (SOTemplate::value_type const& elem : mType->peek ())
{
if (it == mData.end ())
return false;
@@ -1232,381 +1232,6 @@ void STArray::sort (bool (*compare) (const STObject&, const STObject&))
value.sort (compare);
}
std::unique_ptr<STObject> STObject::parseJson (const Json::Value& object, SField::ref inName, int depth)
{
if (!object.isObject ())
throw std::runtime_error ("Value is not an object");
SField::ptr name = &inName;
boost::ptr_vector<SerializedType> data;
Json::Value::Members members (object.getMemberNames ());
for (Json::Value::Members::iterator it = members.begin (), end = members.end (); it != end; ++it)
{
const std::string& fieldName = *it;
const Json::Value& value = object[fieldName];
SField::ref field = SField::getField (fieldName);
if (field == sfInvalid)
throw std::runtime_error ("Unknown field: " + fieldName);
switch (field.fieldType)
{
case STI_UINT8:
if (value.isString ())
{
#if 0
if (field == sfTransactionResult)
{
TER terCode;
if (FUNCTION_THAT_DOESNT_EXIST (value.asString (), terCode))
value = static_cast<int> (terCode);
else
data.push_back (new STUInt8 (field, lexicalCastThrow <unsigned char> (value.asString ())));
}
data.push_back (new STUInt8 (field, lexicalCastThrow <unsigned char> (value.asString ())));
#endif
}
else if (value.isInt ())
{
if (value.asInt () < 0 || value.asInt () > 255)
throw std::runtime_error ("value out of range");
data.push_back (new STUInt8 (field, range_check_cast<unsigned char> (value.asInt (), 0, 255)));
}
else if (value.isUInt ())
{
if (value.asUInt () > 255)
throw std::runtime_error ("value out of range");
data.push_back (new STUInt8 (field, range_check_cast<unsigned char> (value.asUInt (), 0, 255)));
}
else
throw std::runtime_error ("Incorrect type");
break;
case STI_UINT16:
if (value.isString ())
{
std::string strValue = value.asString ();
if (!strValue.empty () && ((strValue[0] < '0') || (strValue[0] > '9')))
{
if (field == sfTransactionType)
{
// Retrieve type from name. Throws if not found.
TxType const txType = TxFormats::getInstance()->findTypeByName (strValue);
data.push_back (new STUInt16 (field, static_cast<uint16> (txType)));
if (*name == sfGeneric)
name = &sfTransaction;
}
else if (field == sfLedgerEntryType)
{
LedgerEntryType const type = LedgerFormats::getInstance()->findTypeByName (strValue);
data.push_back (new STUInt16 (field, static_cast<uint16> (type)));
if (*name == sfGeneric)
name = &sfLedgerEntry;
}
else
throw std::runtime_error ("Invalid field data");
}
else
data.push_back (new STUInt16 (field, lexicalCastThrow <uint16> (strValue)));
}
else if (value.isInt ())
data.push_back (new STUInt16 (field, range_check_cast<uint16> (value.asInt (), 0, 65535)));
else if (value.isUInt ())
data.push_back (new STUInt16 (field, range_check_cast<uint16> (value.asUInt (), 0, 65535)));
else
throw std::runtime_error ("Incorrect type");
break;
case STI_UINT32:
if (value.isString ())
{
data.push_back (new STUInt32 (field, lexicalCastThrow <uint32> (value.asString ())));
}
else if (value.isInt ())
{
data.push_back (new STUInt32 (field, range_check_cast <uint32> (value.asInt (), 0u, 4294967295u)));
}
else if (value.isUInt ())
{
data.push_back (new STUInt32 (field, static_cast<uint32> (value.asUInt ())));
}
else
{
throw std::runtime_error ("Incorrect type");
}
break;
case STI_UINT64:
if (value.isString ())
data.push_back (new STUInt64 (field, uintFromHex (value.asString ())));
else if (value.isInt ())
data.push_back (new STUInt64 (field,
range_check_cast<uint64> (value.asInt (), 0, 18446744073709551615ull)));
else if (value.isUInt ())
data.push_back (new STUInt64 (field, static_cast<uint64> (value.asUInt ())));
else
throw std::runtime_error ("Incorrect type");
break;
case STI_HASH128:
if (value.isString ())
data.push_back (new STHash128 (field, value.asString ()));
else
throw std::runtime_error ("Incorrect type");
break;
case STI_HASH160:
if (value.isString ())
data.push_back (new STHash160 (field, value.asString ()));
else
throw std::runtime_error ("Incorrect type");
break;
case STI_HASH256:
if (value.isString ())
data.push_back (new STHash256 (field, value.asString ()));
else
throw std::runtime_error ("Incorrect type");
break;
case STI_VL:
if (!value.isString ())
throw std::runtime_error ("Incorrect type");
data.push_back (new STVariableLength (field, strUnHex (value.asString ())));
break;
case STI_AMOUNT:
data.push_back (new STAmount (field, value));
break;
case STI_VECTOR256:
if (!value.isArray ())
throw std::runtime_error ("Incorrect type");
{
data.push_back (new STVector256 (field));
STVector256* tail = dynamic_cast<STVector256*> (&data.back ());
assert (tail);
for (Json::UInt i = 0; !object.isValidIndex (i); ++i)
{
uint256 s;
s.SetHex (object[i].asString ());
tail->addValue (s);
}
}
break;
case STI_PATHSET:
if (!value.isArray ())
throw std::runtime_error ("Path set must be array");
{
data.push_back (new STPathSet (field));
STPathSet* tail = dynamic_cast<STPathSet*> (&data.back ());
assert (tail);
for (Json::UInt i = 0; value.isValidIndex (i); ++i)
{
STPath p;
if (!value[i].isArray ())
throw std::runtime_error ("Path must be array");
for (Json::UInt j = 0; value[i].isValidIndex (j); ++j)
{
// each element in this path has some combination of account, currency, or issuer
Json::Value pathEl = value[i][j];
if (!pathEl.isObject ())
throw std::runtime_error ("Path elements must be objects");
const Json::Value& account = pathEl["account"];
const Json::Value& currency = pathEl["currency"];
const Json::Value& issuer = pathEl["issuer"];
bool hasCurrency = false;
uint160 uAccount, uCurrency, uIssuer;
if (!account.isNull ())
{
// human account id
if (!account.isString ())
throw std::runtime_error ("path element accounts must be strings");
std::string strValue = account.asString ();
if (value.size () == 40) // 160-bit hex account value
uAccount.SetHex (strValue);
{
RippleAddress a;
if (!a.setAccountID (strValue))
throw std::runtime_error ("Account in path element invalid");
uAccount = a.getAccountID ();
}
}
if (!currency.isNull ())
{
// human currency
if (!currency.isString ())
throw std::runtime_error ("path element currencies must be strings");
hasCurrency = true;
if (currency.asString ().size () == 40)
uCurrency.SetHex (currency.asString ());
else if (!STAmount::currencyFromString (uCurrency, currency.asString ()))
throw std::runtime_error ("invalid currency");
}
if (!issuer.isNull ())
{
// human account id
if (!issuer.isString ())
throw std::runtime_error ("path element issuers must be strings");
if (issuer.asString ().size () == 40)
uIssuer.SetHex (issuer.asString ());
else
{
RippleAddress a;
if (!a.setAccountID (issuer.asString ()))
throw std::runtime_error ("path element issuer invalid");
uIssuer = a.getAccountID ();
}
}
p.addElement (STPathElement (uAccount, uCurrency, uIssuer, hasCurrency));
}
tail->addPath (p);
}
}
break;
case STI_ACCOUNT:
{
if (!value.isString ())
throw std::runtime_error ("Incorrect type");
std::string strValue = value.asString ();
if (value.size () == 40) // 160-bit hex account value
{
uint160 v;
v.SetHex (strValue);
data.push_back (new STAccount (field, v));
}
else
{
// ripple address
RippleAddress a;
if (!a.setAccountID (strValue))
{
WriteLog (lsINFO, STObject) << "Invalid acccount JSON: " << fieldName << ": " << strValue;
throw std::runtime_error ("Account invalid");
}
data.push_back (new STAccount (field, a.getAccountID ()));
}
}
break;
case STI_OBJECT:
case STI_TRANSACTION:
case STI_LEDGERENTRY:
case STI_VALIDATION:
if (!value.isObject ())
throw std::runtime_error ("Inner value is not an object");
if (depth > 64)
throw std::runtime_error ("Json nest depth exceeded");
data.push_back (parseJson (value, field, depth + 1).release ());
break;
case STI_ARRAY:
if (!value.isArray ())
throw std::runtime_error ("Inner value is not an array");
{
data.push_back (new STArray (field));
STArray* tail = dynamic_cast<STArray*> (&data.back ());
assert (tail);
for (Json::UInt i = 0; value.isValidIndex (i); ++i)
{
bool isObject (value[i].isObject());
bool singleKey (true);
if (isObject)
{
singleKey = value[i].size() == 1;
}
if (!isObject || !singleKey)
{
std::stringstream err;
err << "First level children of `"
<< field.getName()
<< "`must be objects containing a single key with"
<< " an object value";
throw std::runtime_error (err.str());
}
// TODO: There doesn't seem to be a nice way to get just the
// first/only key in an object without copying all keys into
// a vector
std::string objectName (value[i].getMemberNames()[0]);;
SField::ref nameField (SField::getField(objectName));
Json::Value objectFields (value[i][objectName]);
tail->push_back (*STObject::parseJson (objectFields,
nameField,
depth + 1));
}
}
break;
default:
throw std::runtime_error ("Invalid field type");
}
}
return std::unique_ptr<STObject> (new STObject (*name, data));
}
//------------------------------------------------------------------------------
class SerializedObjectTests : public UnitTest
@@ -1656,8 +1281,9 @@ public:
Json::Value faultyJson;
bool parsedOK (parseJSONString(faulty, faultyJson));
unexpected(!parsedOK, "failed to parse");
so = STObject::parseJson (faultyJson);
fail ("It should have thrown. "
STParsedJSON parsed ("test", faultyJson);
expect (parsed.object.get() == nullptr,
"It should have thrown. "
"Immediate children of STArray encoded as json must "
"have one key only.");
}
@@ -1677,16 +1303,15 @@ public:
bool parsedOK (parseJSONString(json, jsonObject));
if (parsedOK)
{
std::unique_ptr<STObject> so;
so = STObject::parseJson (jsonObject);
Json::FastWriter writer;
std::string const& serialized (writer.write(so->getJson(0)));
bool serializedOK = serialized == json;
unexpected (!serializedOK, serialized + " should equal: " + json);
STParsedJSON parsed ("test", jsonObject);
Json::FastWriter writer;
std::string const& serialized (
writer.write (parsed.object->getJson(0)));
expect (serialized == json, serialized + " should equal: " + json);
}
else
{
fail ("Couldn't parse json: " + json);
fail ("Couldn't parse json: " + json);
}
}

View File

@@ -48,17 +48,17 @@ public:
setType (type);
}
std::unique_ptr<STObject> oClone () const
STObject (SField::ref name, boost::ptr_vector<SerializedType>& data) : SerializedType (name), mType (NULL)
{
mData.swap (data);
}
std::unique_ptr <STObject> oClone () const
{
return std::unique_ptr<STObject> (new STObject (*this));
}
static std::unique_ptr<STObject> parseJson (const Json::Value & value, SField::ref name = sfGeneric, int depth = 0);
virtual ~STObject ()
{
;
}
virtual ~STObject () { }
static std::unique_ptr<SerializedType> deserialize (SerializerIterator & sit, SField::ref name);
@@ -295,26 +295,11 @@ private:
}
*/
// VFALCO TODO these parameters should not be const references.
template <typename T, typename U>
static T range_check_cast (const U& value, const T& minimum, const T& maximum)
{
if ((value < minimum) || (value > maximum))
throw std::runtime_error ("Value out of range");
return static_cast<T> (value);
}
STObject* duplicate () const
{
return new STObject (*this);
}
STObject (SField::ref name, boost::ptr_vector<SerializedType>& data) : SerializedType (name), mType (NULL)
{
mData.swap (data);
}
private:
boost::ptr_vector<SerializedType> mData;
const SOTemplate* mType;
@@ -322,6 +307,16 @@ private:
//------------------------------------------------------------------------------
// VFALCO TODO these parameters should not be const references.
template <typename T, typename U>
static T range_check_cast (const U& value, const T& minimum, const T& maximum)
{
if ((value < minimum) || (value > maximum))
throw std::runtime_error ("Value out of range");
return static_cast<T> (value);
}
inline STObject::iterator range_begin (STObject& x)
{
return x.begin ();

View File

@@ -47,7 +47,7 @@ void SOTemplate::push_back (SOElement const& r)
// Append the new element.
//
mTypes.push_back (new SOElement (r));
mTypes.push_back (value_type (new SOElement (r)));
}
int SOTemplate::getIndex (SField::ref f) const

View File

@@ -53,18 +53,18 @@ public:
//------------------------------------------------------------------------------
/** Defines the fields and their attributes within a SerializedObject.
Each subclass of SerializedObject will provide its own template
describing the available fields and their metadata attributes.
*/
class SOTemplate
{
public:
/** Create an empty template.
typedef std::unique_ptr <SOElement const> value_type;
typedef std::vector <value_type> list_type;
/** Create an empty template.
After creating the template, call @ref push_back with the
desired fields.
@see push_back
*/
SOTemplate ();
@@ -72,21 +72,19 @@ public:
// VFALCO NOTE Why do we even bother with the 'private' keyword if
// this function is present?
//
std::vector <SOElement const*> const& peek () const
list_type const& peek () const
{
return mTypes;
}
/** Add an element to the template.
*/
/** Add an element to the template. */
void push_back (SOElement const& r);
/** Retrieve the position of a named field.
*/
/** Retrieve the position of a named field. */
int getIndex (SField::ref) const;
private:
std::vector <SOElement const*> mTypes;
list_type mTypes;
std::vector <int> mIndex; // field num -> index
};

View File

@@ -65,6 +65,20 @@ static inline const uint160& get_u160_one ()
#define ACCOUNT_XRP get_u160_zero()
#define ACCOUNT_ONE get_u160_one() // Used as a place holder.
//------------------------------------------------------------------------------
/** A type which can be exported to a well known binary format.
A SerializedType:
- Always a field
- Can always go inside an eligible enclosing SerializedType
(such as STArray)
- Has a field name
Like JSON, a SerializedObject is a basket which has rules
on what it can hold.
*/
// VFALCO TODO Document this as it looks like a central class.
// STObject is derived from it
//
@@ -88,6 +102,9 @@ public:
return std::unique_ptr<SerializedType> (new SerializedType (name));
}
/** A SerializeType is a field.
This sets the name.
*/
void setFName (SField::ref n)
{
fName = &n;
@@ -160,19 +177,27 @@ private:
}
};
//------------------------------------------------------------------------------
inline SerializedType* new_clone (const SerializedType& s)
{
return s.clone ().release ();
SerializedType* const copy (s.clone ().release ());
assert (typeid (*copy) == typeid (s));
return copy;
}
inline void delete_clone (const SerializedType* s)
{
boost::checked_delete (s);
}
inline std::ostream& operator<< (std::ostream& out, const SerializedType& t)
{
return out << t.getFullText ();
}
//------------------------------------------------------------------------------
class STUInt8 : public SerializedType
{
public:
@@ -230,6 +255,8 @@ private:
static STUInt8* construct (SerializerIterator&, SField::ref f);
};
//------------------------------------------------------------------------------
class STUInt16 : public SerializedType
{
public:
@@ -287,6 +314,8 @@ private:
static STUInt16* construct (SerializerIterator&, SField::ref name);
};
//------------------------------------------------------------------------------
class STUInt32 : public SerializedType
{
public:
@@ -344,10 +373,11 @@ private:
static STUInt32* construct (SerializerIterator&, SField::ref name);
};
//------------------------------------------------------------------------------
class STUInt64 : public SerializedType
{
public:
STUInt64 (uint64 v = 0) : value (v)
{
;
@@ -401,6 +431,8 @@ private:
static STUInt64* construct (SerializerIterator&, SField::ref name);
};
//------------------------------------------------------------------------------
// Internal form:
// 1: If amount is zero, then value is zero and offset is -100
// 2: Otherwise:
@@ -805,6 +837,8 @@ private:
extern const STAmount saZero;
extern const STAmount saOne;
//------------------------------------------------------------------------------
class STHash128 : public SerializedType
{
public:
@@ -876,6 +910,8 @@ private:
static STHash128* construct (SerializerIterator&, SField::ref name);
};
//------------------------------------------------------------------------------
class STHash160 : public SerializedType
{
public:
@@ -947,6 +983,8 @@ private:
static STHash160* construct (SerializerIterator&, SField::ref name);
};
//------------------------------------------------------------------------------
class STHash256 : public SerializedType
{
public:
@@ -1018,6 +1056,8 @@ private:
static STHash256* construct (SerializerIterator&, SField::ref);
};
//------------------------------------------------------------------------------
// variable length byte string
class STVariableLength : public SerializedType
{
@@ -1091,6 +1131,8 @@ private:
static STVariableLength* construct (SerializerIterator&, SField::ref);
};
//------------------------------------------------------------------------------
class STAccount : public STVariableLength
{
public:
@@ -1137,6 +1179,8 @@ private:
static STAccount* construct (SerializerIterator&, SField::ref);
};
//------------------------------------------------------------------------------
class STPathElement
{
private:
@@ -1223,6 +1267,8 @@ private:
uint160 mIssuerID;
};
//------------------------------------------------------------------------------
class STPath
{
public:
@@ -1322,6 +1368,8 @@ inline std::vector<STPathElement>::const_iterator range_end (const STPath& x)
return x.end ();
}
//------------------------------------------------------------------------------
// A set of zero or more payment paths
class STPathSet : public SerializedType
{
@@ -1482,6 +1530,8 @@ inline std::vector<STPath>::const_iterator range_end (const STPathSet& x)
return x.end ();
}
//------------------------------------------------------------------------------
class STVector256 : public SerializedType
{
public:
@@ -1582,4 +1632,3 @@ private:
};
#endif
// vim:ts=4

View File

@@ -44,6 +44,7 @@
#include <openssl/err.h>
#include "../ripple/sslutil/ripple_sslutil.h"
#include "../ripple/rpc/api/ErrorCodes.h"
// VFALCO TODO fix these warnings!
#if BEAST_MSVC
@@ -55,6 +56,8 @@
#undef min
#endif
#include "protocol/STParsedJSON.cpp"
namespace ripple
{

View File

@@ -51,6 +51,8 @@ namespace ripple {
}
#include "protocol/STParsedJSON.h"
//------------------------------------------------------------------------------
namespace boost