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

@@ -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);
}
}