Added concept of type to JsonString. Appending single items or key/value pairs is now type-safe and only allowed in certain cases. JsonString will refuse to produce invalid JSON. All duplicative functions have been replaced with templates. Encoding of values uses json_spirit's value writer which should be no worse performance than format() and it will escape everything properly. Final string form is now built directly using knowledge of type, such as when an instance becomes an Array or Object the appropriate opening character is written. This avoids a full copy just to prepend the opening character later. Index interface for key/value pairs no longer makes a temporary copy of the key string. JsonString is now only needed by Status.actor.cpp. Still more work to be done here.
This commit is contained in:
parent
54989bbc63
commit
ce3f01a0cf
|
@ -27,7 +27,6 @@
|
|||
#include "Status.h"
|
||||
#include "ClientDBInfo.h"
|
||||
#include "ClientWorkerInterface.h"
|
||||
#include "JsonString.h"
|
||||
|
||||
struct ClusterInterface {
|
||||
RequestStream< struct OpenDatabaseRequest > openDatabase;
|
||||
|
@ -193,7 +192,7 @@ struct StatusReply {
|
|||
|
||||
StatusReply() {}
|
||||
explicit StatusReply(StatusObject obj) : statusObj(obj), statusStr(json_spirit::write_string(json_spirit::mValue(obj))) {}
|
||||
explicit StatusReply(JsonString obj) : statusStr(obj.getJson()) {}
|
||||
explicit StatusReply(std::string &&text) : statusStr(text) {}
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
|
|
|
@ -1,355 +1,9 @@
|
|||
#include "JsonString.h"
|
||||
#include <iostream>
|
||||
|
||||
std::string format( const char* form, ... );
|
||||
|
||||
bool shouldEscape(char c) {
|
||||
switch( c ) {
|
||||
case '"':
|
||||
case '\\':
|
||||
case '\b':
|
||||
case '\f':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void escape( const std::string& in, std::string& out ) {
|
||||
int beginCopy = 0;
|
||||
for (int i = 0; i < in.size(); i++) {
|
||||
if (shouldEscape(in[i])) {
|
||||
out.append(in, beginCopy, i - beginCopy);
|
||||
beginCopy = i + 1;
|
||||
out += '\\';
|
||||
out += in[i];
|
||||
}
|
||||
}
|
||||
if(beginCopy < in.size()) {
|
||||
out.append(in, beginCopy, in.size() - beginCopy);
|
||||
}
|
||||
}
|
||||
|
||||
JsonString::JsonString() : hasKey(false) {
|
||||
}
|
||||
JsonString::JsonString( const JsonString& jsonString) : _jsonText(jsonString._jsonText), hasKey(jsonString.hasKey) {
|
||||
}
|
||||
JsonString::JsonString( const JsonStringArray& jsonArray) : hasKey(false) {
|
||||
append(jsonArray);
|
||||
}
|
||||
|
||||
JsonString::JsonString( const std::string& value ) : hasKey(false) {
|
||||
append(value);
|
||||
}
|
||||
JsonString::JsonString( const char* value ) : hasKey(false) {
|
||||
append(value);
|
||||
}
|
||||
|
||||
JsonString::JsonString( const json_spirit::mObject& value ) : hasKey(false) {
|
||||
_jsonText = json_spirit::write_string(json_spirit::mValue(value));
|
||||
_jsonText = _jsonText.substr(1,_jsonText.size()-2); //remove outer {}
|
||||
hasKey = !_jsonText.empty();
|
||||
}
|
||||
|
||||
JsonString::JsonString( const std::string& name, const std::string& value ) : hasKey(false) {
|
||||
append(name, value);
|
||||
}
|
||||
JsonString::JsonString( const std::string& name, const char* value ) : hasKey(false) {
|
||||
append(name, value);
|
||||
}
|
||||
JsonString::JsonString( const std::string& name, double value ) : hasKey(false) {
|
||||
append(name, value);
|
||||
}
|
||||
JsonString::JsonString( const std::string& name, long int value ) : hasKey(false) {
|
||||
append(name, value);
|
||||
}
|
||||
JsonString::JsonString( const std::string& name, long unsigned int value ) : hasKey(false) {
|
||||
append(name, value);
|
||||
}
|
||||
JsonString::JsonString( const std::string& name, long long int value ) : hasKey(false) {
|
||||
append(name, value);
|
||||
}
|
||||
JsonString::JsonString( const std::string& name, long long unsigned int value ) : hasKey(false) {
|
||||
append(name, value);
|
||||
}
|
||||
JsonString::JsonString( const std::string& name, int value ) : hasKey(false) {
|
||||
append(name, value);
|
||||
}
|
||||
JsonString::JsonString( const std::string& name, unsigned value ) : hasKey(false) {
|
||||
append(name, value);
|
||||
}
|
||||
JsonString::JsonString( const std::string& name, bool value ) : hasKey(false) {
|
||||
append(name, value);
|
||||
}
|
||||
JsonString::JsonString( const std::string& name, const JsonString& value ) : hasKey(false) {
|
||||
append(name, value);
|
||||
}
|
||||
JsonString::JsonString( const std::string& name, const JsonStringArray& value ) : hasKey(false) {
|
||||
append(name, value);
|
||||
}
|
||||
|
||||
JsonString JsonString::makeMessage(const char *name, const char *description) {
|
||||
JsonString out;
|
||||
out["name"] = name;
|
||||
out["description"] = description;
|
||||
return out;
|
||||
}
|
||||
|
||||
JsonString& JsonString::appendImpl( const std::string& name, const std::string& value, bool quote ) {
|
||||
_jsonText.reserve(_jsonText.size() + name.size() + (quote ? (2*value.size() + 6) : (value.size() + 4)));
|
||||
hasKey = true;
|
||||
if(!_jsonText.empty()) {
|
||||
_jsonText += ',';
|
||||
}
|
||||
_jsonText += '"';
|
||||
_jsonText += name;
|
||||
_jsonText += "\":";
|
||||
if (quote) {
|
||||
_jsonText += "\"";
|
||||
escape(value, _jsonText);
|
||||
_jsonText += "\"";
|
||||
} else {
|
||||
_jsonText += value;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
JsonString& JsonString::appendImpl( const std::string& value, bool quote ) {
|
||||
_jsonText.reserve(_jsonText.size() + (quote ? (2*value.size() + 3) : (value.size() + 1)));
|
||||
if(!_jsonText.empty()) {
|
||||
_jsonText += ',';
|
||||
}
|
||||
if (quote) {
|
||||
_jsonText += "\"";
|
||||
escape(value, _jsonText);
|
||||
_jsonText += "\"";
|
||||
} else {
|
||||
_jsonText += value;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string JsonString::stringify(const char* value) {
|
||||
return std::string(value);
|
||||
}
|
||||
std::string JsonString::stringify(double value) {
|
||||
return format("%g", value);
|
||||
}
|
||||
std::string JsonString::stringify(long int value) {
|
||||
return format("%ld", value);
|
||||
}
|
||||
std::string JsonString::stringify(long unsigned int value) {
|
||||
return format("%lu", value);
|
||||
}
|
||||
std::string JsonString::stringify(long long int value) {
|
||||
return format("%lld", value);
|
||||
}
|
||||
std::string JsonString::stringify(long long unsigned int value) {
|
||||
return format("%llu", value);
|
||||
}
|
||||
std::string JsonString::stringify(int value) {
|
||||
return format("%d", value);
|
||||
}
|
||||
std::string JsonString::stringify(unsigned value) {
|
||||
return format("%u", value);
|
||||
}
|
||||
std::string JsonString::stringify(bool value) {
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
|
||||
JsonString& JsonString::append( const std::string& name, const std::string& value ) {
|
||||
return appendImpl(name, value, true);
|
||||
}
|
||||
JsonString& JsonString::append( const std::string& name, const char* value ) {
|
||||
return appendImpl(name, stringify(value), true);
|
||||
}
|
||||
JsonString& JsonString::append( const std::string& name, double value ) {
|
||||
return appendImpl(name, stringify(value), false);
|
||||
}
|
||||
JsonString& JsonString::append( const std::string& name, long int value ) {
|
||||
return appendImpl(name, stringify(value), false);
|
||||
}
|
||||
JsonString& JsonString::append( const std::string& name, long unsigned int value ) {
|
||||
return appendImpl(name, stringify(value), false);
|
||||
}
|
||||
JsonString& JsonString::append( const std::string& name, long long int value ) {
|
||||
return appendImpl(name, stringify(value), false);
|
||||
}
|
||||
JsonString& JsonString::append( const std::string& name, long long unsigned int value ) {
|
||||
return appendImpl(name, stringify(value), false);
|
||||
}
|
||||
JsonString& JsonString::append( const std::string& name, int value ) {
|
||||
return appendImpl(name, stringify(value), false);
|
||||
}
|
||||
JsonString& JsonString::append( const std::string& name, unsigned value ) {
|
||||
return appendImpl(name, stringify(value), false);
|
||||
}
|
||||
JsonString& JsonString::append( const std::string& name, bool value ) {
|
||||
return appendImpl(name, stringify(value), false);
|
||||
}
|
||||
JsonString& JsonString::append( const std::string& name, const JsonString& value ) {
|
||||
_jsonText.reserve(_jsonText.size() + name.size() + value._jsonText.size() + 6);
|
||||
hasKey = true;
|
||||
if(!_jsonText.empty()) {
|
||||
_jsonText += ',';
|
||||
}
|
||||
_jsonText += '\"';
|
||||
_jsonText += name;
|
||||
_jsonText += "\":{";
|
||||
_jsonText += value._jsonText;
|
||||
_jsonText += '}';
|
||||
return *this;
|
||||
}
|
||||
JsonString& JsonString::append( const std::string& name, const JsonStringArray& values ) {
|
||||
int valueBytes = 0;
|
||||
for (auto const& value : values) {
|
||||
valueBytes += value.getLength();
|
||||
}
|
||||
_jsonText.reserve(_jsonText.size() + name.size() + values.size() + valueBytes + 6);
|
||||
hasKey = true;
|
||||
if(!_jsonText.empty()) {
|
||||
_jsonText += ',';
|
||||
}
|
||||
_jsonText += '"';
|
||||
_jsonText += name;
|
||||
_jsonText += "\":[";
|
||||
bool first = true;
|
||||
for (auto const& value : values) {
|
||||
if (!first) {
|
||||
_jsonText += ',';
|
||||
}
|
||||
_jsonText += value.getJson();
|
||||
first = false;
|
||||
}
|
||||
_jsonText += ']';
|
||||
return *this;
|
||||
}
|
||||
|
||||
JsonString& JsonString::append( const std::string& value ) {
|
||||
return appendImpl(value, true);
|
||||
}
|
||||
JsonString& JsonString::append( const char* value ) {
|
||||
return appendImpl(stringify(value), true);
|
||||
}
|
||||
JsonString& JsonString::append( double value ) {
|
||||
return appendImpl(stringify(value), false);
|
||||
}
|
||||
JsonString& JsonString::append( long int value ) {
|
||||
return appendImpl(stringify(value), false);
|
||||
}
|
||||
JsonString& JsonString::append( long unsigned int value ) {
|
||||
return appendImpl(stringify(value), false);
|
||||
}
|
||||
JsonString& JsonString::append( long long int value ) {
|
||||
return appendImpl(stringify(value), false);
|
||||
}
|
||||
JsonString& JsonString::append( long long unsigned int value ) {
|
||||
return appendImpl(stringify(value), false);
|
||||
}
|
||||
JsonString& JsonString::append( int value ) {
|
||||
return appendImpl(stringify(value), false);
|
||||
}
|
||||
JsonString& JsonString::append( unsigned value ) {
|
||||
return appendImpl(stringify(value), false);
|
||||
}
|
||||
JsonString& JsonString::append( bool value ) {
|
||||
return appendImpl(stringify(value), false);
|
||||
}
|
||||
JsonString& JsonString::append( const JsonString& value ) {
|
||||
// Only do something, if not empty
|
||||
if (!value.empty()) {
|
||||
_jsonText.reserve(_jsonText.size() + value._jsonText.size() + 1);
|
||||
if (!_jsonText.empty()) {
|
||||
_jsonText += ',';
|
||||
}
|
||||
_jsonText += value._jsonText;
|
||||
if(value.hasKey) {
|
||||
hasKey = true;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
JsonString& JsonString::append( const JsonStringArray& values ) {
|
||||
int valueBytes = 0;
|
||||
for (auto const& value : values) {
|
||||
valueBytes += value.getLength();
|
||||
}
|
||||
_jsonText.reserve(_jsonText.size() + values.size() + valueBytes + 3);
|
||||
if (!_jsonText.empty()) {
|
||||
_jsonText += ',';
|
||||
}
|
||||
_jsonText += '[';
|
||||
bool first = true;
|
||||
for (auto const& value : values) {
|
||||
if (!first) {
|
||||
_jsonText += ',';
|
||||
}
|
||||
_jsonText += value.getJson();
|
||||
first = false;
|
||||
}
|
||||
_jsonText += ']';
|
||||
return *this;
|
||||
}
|
||||
|
||||
JsonString& JsonString::clear() {
|
||||
_jsonText.clear();
|
||||
hasKey = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool JsonString::empty() const {
|
||||
return _jsonText.empty();
|
||||
}
|
||||
|
||||
const std::string& JsonString::getJsonText() const {
|
||||
return _jsonText;
|
||||
}
|
||||
|
||||
size_t JsonString::getLength() const {
|
||||
return _jsonText.length() + ((!empty() && !hasKey) ? 0 : 2);
|
||||
}
|
||||
|
||||
std::string JsonString::getJson() const {
|
||||
// If not empty with no names (only values), don't add brackets because prob in an array
|
||||
if (!empty() && !hasKey) {
|
||||
return _jsonText;
|
||||
} else {
|
||||
std::string result;
|
||||
result.reserve(_jsonText.size() + 2);
|
||||
result += '{';
|
||||
result += _jsonText;
|
||||
result += '}';
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
JsonString& JsonString::copy( const JsonString& jsonString ) {
|
||||
_jsonText = jsonString._jsonText;
|
||||
hasKey = jsonString.hasKey;
|
||||
return *this;
|
||||
}
|
||||
|
||||
JsonString& JsonString::operator=( const JsonString& jsonString ) {
|
||||
return copy(jsonString);
|
||||
}
|
||||
|
||||
int JsonString::compare( const JsonString& jsonString ) const {
|
||||
return jsonString._jsonText.compare(_jsonText);
|
||||
}
|
||||
|
||||
bool JsonString::equals( const JsonString& jsonString ) const {
|
||||
return (compare(jsonString) == 0);
|
||||
}
|
||||
|
||||
|
||||
JsonStringArray::JsonStringArray() {
|
||||
}
|
||||
JsonStringArray::JsonStringArray( const JsonStringArray& arr) : std::vector<JsonString>(arr.begin(), arr.end()) {
|
||||
}
|
||||
JsonStringArray::~JsonStringArray() {
|
||||
}
|
||||
|
||||
JsonStringSetter::JsonStringSetter( JsonString& jsonString, const std::string& name ) : _jsonString(jsonString), _name(name) {
|
||||
}
|
||||
|
|
|
@ -9,141 +9,190 @@
|
|||
#include "fdbrpc/JSONDoc.h"
|
||||
|
||||
class JsonString;
|
||||
class JsonStringArray;
|
||||
class JsonStringSetter;
|
||||
template <typename T> class JsonStringSetter;
|
||||
|
||||
// Class for building JSON string values.
|
||||
// Default value is 'empty' which is different from JSON null
|
||||
class JsonString {
|
||||
public:
|
||||
JsonString();
|
||||
JsonString( const JsonString& jsonString);
|
||||
JsonString( JsonString&& jsonString) = default;
|
||||
explicit JsonString( const JsonStringArray& jsonArray);
|
||||
explicit JsonString( const char* value ); // Used to define values (used in an Array)
|
||||
explicit JsonString( const std::string& value ); // Used to define values (used in an Array)
|
||||
explicit JsonString( const json_spirit::mObject& value );
|
||||
public:
|
||||
enum EType { EMPTY, NULLVALUE, SCALAR, OBJECT, ARRAY, FINAL, DESTROYED };
|
||||
JsonString(EType type = EType::EMPTY) : type(type)
|
||||
{
|
||||
if(type == EType::NULLVALUE) {
|
||||
write("null");
|
||||
}
|
||||
else if(type == EType::ARRAY) {
|
||||
write("[");
|
||||
}
|
||||
else if(type == EType::OBJECT) {
|
||||
write("{");
|
||||
}
|
||||
}
|
||||
|
||||
JsonString( const std::string& name, const char* value );
|
||||
JsonString( const std::string& name, const std::string& value );
|
||||
JsonString( const std::string& name, double value );
|
||||
JsonString( const std::string& name, long int value );
|
||||
JsonString( const std::string& name, long unsigned int value );
|
||||
JsonString( const std::string& name, long long int value );
|
||||
JsonString( const std::string& name, long long unsigned int value );
|
||||
JsonString( const std::string& name, int value );
|
||||
JsonString( const std::string& name, unsigned value );
|
||||
JsonString( const std::string& name, bool value );
|
||||
JsonString( const std::string& name, const JsonString& value );
|
||||
JsonString( const std::string& name, const JsonStringArray& value );
|
||||
int empty() const {
|
||||
return type == EType::EMPTY || (jsonText.size() == 1 && (type == EType::ARRAY || type == EType::OBJECT));
|
||||
}
|
||||
|
||||
void clear() {
|
||||
type = EMPTY;
|
||||
jsonText.clear();
|
||||
}
|
||||
|
||||
JsonString& append( const std::string& name, const char* value );
|
||||
JsonString& append( const std::string& name, const std::string& value );
|
||||
JsonString& append( const std::string& name, double value );
|
||||
JsonString& append( const std::string& name, long int value );
|
||||
JsonString& append( const std::string& name, long unsigned int value );
|
||||
JsonString& append( const std::string& name, long long int value );
|
||||
JsonString& append( const std::string& name, long long unsigned int value );
|
||||
JsonString& append( const std::string& name, int value );
|
||||
JsonString& append( const std::string& name, unsigned value );
|
||||
JsonString& append( const std::string& name, bool value );
|
||||
JsonString& append( const std::string& name, const JsonString& value );
|
||||
JsonString& append( const std::string& name, const JsonStringArray& value );
|
||||
// Finish the JSON value - close array or object if still open and set final state in either case
|
||||
// Scalar and null types stay as they are
|
||||
void finalize() {
|
||||
ASSERT(type != EType::DESTROYED);
|
||||
if(type != EType::FINAL) {
|
||||
if(type == EType::ARRAY) {
|
||||
write("]");
|
||||
type = EType::FINAL;
|
||||
}
|
||||
else if(type == EType::OBJECT) {
|
||||
write("}");
|
||||
type = EType::FINAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JsonString& append( const std::string& value );
|
||||
JsonString& append( const char* value );
|
||||
JsonString& append( const JsonStringArray& value );
|
||||
int getFinalLength() {
|
||||
finalize();
|
||||
return jsonText.size();
|
||||
}
|
||||
|
||||
JsonString& append( double value );
|
||||
JsonString& append( long int value );
|
||||
JsonString& append( long unsigned int value );
|
||||
JsonString& append( long long int value );
|
||||
JsonString& append( long long unsigned int value );
|
||||
JsonString& append( int value );
|
||||
JsonString& append( unsigned value );
|
||||
JsonString& append( bool value );
|
||||
JsonString& append( const JsonString& value );
|
||||
std::string getJson() {
|
||||
finalize();
|
||||
return jsonText;
|
||||
}
|
||||
|
||||
JsonStringSetter operator[]( const std::string& name );
|
||||
JsonStringSetter operator[]( const char* name );
|
||||
// Can add value to an array or turn an empty into a scalar
|
||||
template<typename VT> inline JsonString & append(VT &&val) {
|
||||
if(type == EType::ARRAY) {
|
||||
if(jsonText.size() != 1)
|
||||
write(",");
|
||||
}
|
||||
else {
|
||||
ASSERT(type == EType::EMPTY);
|
||||
type = EType::SCALAR;
|
||||
}
|
||||
|
||||
int compare( const JsonString& jsonString ) const;
|
||||
bool equals( const JsonString& jsonString ) const;
|
||||
bool operator==( const JsonString& jsonString ) const { return equals(jsonString); }
|
||||
bool operator!=( const JsonString& jsonString ) const { return !equals(jsonString); }
|
||||
|
||||
JsonString& copy( const JsonString& jsonString );
|
||||
JsonString& operator=( const JsonString& jsonString );
|
||||
|
||||
std::string getJson() const;
|
||||
size_t getLength() const;
|
||||
|
||||
JsonString& clear();
|
||||
bool empty() const;
|
||||
static JsonString makeMessage(const char *name, const char *description);
|
||||
|
||||
protected:
|
||||
JsonString& appendImpl( const std::string& name, const std::string& value, bool quote);
|
||||
JsonString& appendImpl( const std::string& value, bool quote);
|
||||
|
||||
static std::string stringify(const char* value);
|
||||
static std::string stringify(double value);
|
||||
static std::string stringify(long int value);
|
||||
static std::string stringify(long unsigned int value);
|
||||
static std::string stringify(long long int value);
|
||||
static std::string stringify(long long unsigned int value);
|
||||
static std::string stringify(int value);
|
||||
static std::string stringify(unsigned value);
|
||||
static std::string stringify(bool value);
|
||||
|
||||
protected:
|
||||
std::string _jsonText;
|
||||
bool hasKey;
|
||||
|
||||
// Uneditted text
|
||||
const std::string& getJsonText() const;
|
||||
};
|
||||
|
||||
// Make protected because no virtual destructor
|
||||
class JsonStringArray : protected std::vector<JsonString>
|
||||
{
|
||||
typedef JsonString T;
|
||||
typedef std::vector<JsonString> vector;
|
||||
public:
|
||||
using vector::push_back;
|
||||
using vector::operator[];
|
||||
using vector::begin;
|
||||
using vector::end;
|
||||
using vector::size;
|
||||
using vector::empty;
|
||||
using vector::clear;
|
||||
public:
|
||||
JsonStringArray();
|
||||
JsonStringArray( const JsonStringArray& jsonStringArray);
|
||||
JsonStringArray( JsonStringArray&& jsonStringArray) = default;
|
||||
virtual ~JsonStringArray();
|
||||
};
|
||||
|
||||
class JsonStringSetter {
|
||||
public:
|
||||
|
||||
JsonStringSetter( JsonString& jsonString, const std::string& name );
|
||||
|
||||
template <class valClass>
|
||||
JsonStringSetter& operator=( const valClass& value ) {
|
||||
_jsonString.append(_name, value);
|
||||
writeValue(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
JsonString& _jsonString;
|
||||
std::string _name;
|
||||
template<typename KT, typename VT> inline JsonString & append(KT &&name, VT &&val) {
|
||||
if(type == EType::EMPTY) {
|
||||
type = EType::OBJECT;
|
||||
write("{");
|
||||
}
|
||||
else {
|
||||
ASSERT(type == EType::OBJECT);
|
||||
if(jsonText.size() != 1)
|
||||
write(",");
|
||||
}
|
||||
|
||||
write("\"");
|
||||
write(name);
|
||||
write("\":");
|
||||
// The easy alternative to this is using format() which also creates a temporary string, so let's
|
||||
// let json_spirit handle stringification of native types for now.
|
||||
writeValue(val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T> inline JsonStringSetter<T> operator[](T &&name);
|
||||
|
||||
static JsonString makeMessage(const char *name, const char *description);
|
||||
|
||||
protected:
|
||||
EType type;
|
||||
std::string jsonText;
|
||||
|
||||
template<typename T> inline void write(T &&s) {
|
||||
jsonText.append(std::forward<T>(s));
|
||||
}
|
||||
|
||||
template<typename T> inline void writeValue(T &&val) {
|
||||
write(json_spirit::write_string(json_spirit::mValue(val)));
|
||||
}
|
||||
};
|
||||
|
||||
inline JsonStringSetter JsonString::operator[]( const std::string& name ) {
|
||||
return JsonStringSetter(*this, name);
|
||||
// A JsonString is already a proper JSON string (or empty)
|
||||
template<> inline void JsonString::writeValue(const JsonString &val) {
|
||||
// Convert empty to null because in this context we must write something.
|
||||
if(val.type == EType::EMPTY) {
|
||||
write("null");
|
||||
}
|
||||
else {
|
||||
write(const_cast<JsonString &>(val).getJson());
|
||||
}
|
||||
}
|
||||
|
||||
inline JsonStringSetter JsonString::operator[]( const char* name ) {
|
||||
return JsonStringSetter(*this, name);
|
||||
template<> inline void JsonString::writeValue(JsonString &val) {
|
||||
writeValue<const JsonString &>(val);
|
||||
}
|
||||
|
||||
// "Deep" append of mObject based on type of this
|
||||
// Add kv pairs individually for empty or object, add as a closed object for array
|
||||
template<> inline JsonString & JsonString::append(const json_spirit::mObject &obj) {
|
||||
if(type == EType::EMPTY || type == EType::OBJECT) {
|
||||
for(auto &kv : obj) {
|
||||
append(kv.first, kv.second);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ASSERT(type == EType::ARRAY);
|
||||
append(json_spirit::mValue(obj));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Starts as an Array type and adds push_back interface to appear array-like for writing
|
||||
class JsonStringArray : protected JsonString {
|
||||
public:
|
||||
JsonStringArray() : JsonString(EType::ARRAY) {}
|
||||
|
||||
template<typename VT> void push_back(VT &&o) {
|
||||
append(std::forward<VT>(o));
|
||||
++count;
|
||||
}
|
||||
|
||||
// Clear but maintain Array-ness
|
||||
void clear() {
|
||||
clear();
|
||||
type = EType::ARRAY;
|
||||
}
|
||||
|
||||
int size() const {
|
||||
return count;
|
||||
}
|
||||
|
||||
protected:
|
||||
int count;
|
||||
};
|
||||
|
||||
// A JsonString is already a proper JSON string (or empty)
|
||||
template<> inline void JsonString::writeValue(JsonStringArray &val) {
|
||||
writeValue((JsonString &)val);
|
||||
}
|
||||
|
||||
// Template is the key name, accepted as an r-value to avoid copying if it's a string
|
||||
template<typename KT>
|
||||
class JsonStringSetter {
|
||||
public:
|
||||
JsonStringSetter( JsonString& dest, KT &&name) : dest(dest), name(std::forward<KT>(name)) {}
|
||||
|
||||
template <class VT> inline JsonStringSetter& operator=(VT &&value) {
|
||||
dest.append(name, std::forward<VT>(value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
JsonString& dest;
|
||||
KT name;
|
||||
};
|
||||
|
||||
template<typename T> inline JsonStringSetter<T> JsonString::operator[](T &&name) {
|
||||
return JsonStringSetter<T>(*this, std::forward<T>(name));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -23,6 +23,11 @@
|
|||
|
||||
#include "../fdbrpc/JSONDoc.h"
|
||||
|
||||
// Reads the entire string s as a JSON value
|
||||
// Throws if no value can be parsed or if s contains data after the first JSON value
|
||||
// Trailing whitespace in s is allowed
|
||||
json_spirit::mValue readJSONStrictly(const std::string s);
|
||||
|
||||
struct StatusObject : json_spirit::mObject {
|
||||
typedef json_spirit::mObject Map;
|
||||
typedef json_spirit::mArray Array;
|
||||
|
|
|
@ -32,6 +32,23 @@
|
|||
#include "flow/UnitTest.h"
|
||||
#include "QuietDatabase.h"
|
||||
#include "RecoveryState.h"
|
||||
#include "fdbclient/JsonString.h"
|
||||
|
||||
json_spirit::mValue readJSONStrictly(const std::string s) {
|
||||
json_spirit::mValue val;
|
||||
std::string::const_iterator i = s.begin();
|
||||
if(!json_spirit::read_range(i, s.end(), val))
|
||||
throw json_malformed();
|
||||
|
||||
// Allow trailing whitespace
|
||||
while(i != s.end()) {
|
||||
if(!isspace(*i))
|
||||
throw json_eof_expected();
|
||||
++i;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
const char* RecoveryStatus::names[] = {
|
||||
"reading_coordinated_state", "locking_coordinated_state", "locking_old_transaction_servers", "reading_transaction_system_state",
|
||||
|
@ -233,7 +250,7 @@ static JsonString getLocalityInfo(const LocalityData& locality) {
|
|||
localityObj[it->first.toString()] = it->second.get().toString();
|
||||
}
|
||||
else {
|
||||
localityObj[it->first.toString()] = JsonString();
|
||||
localityObj[it->first.toString()] = JsonString(JsonString::NULLVALUE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1075,7 +1092,7 @@ static JsonString configurationFetcher(Optional<DatabaseConfiguration> conf, Ser
|
|||
try {
|
||||
if(conf.present()) {
|
||||
DatabaseConfiguration configuration = conf.get();
|
||||
statusObj = JsonString( configuration.toJSON() );
|
||||
statusObj.append(configuration.toJSON());
|
||||
|
||||
JsonStringArray excludedServersArr;
|
||||
std::set<AddressExclusion> excludedServers = configuration.getExcludedServers();
|
||||
|
@ -1623,7 +1640,7 @@ static JsonStringArray getClientIssuesAsMessages( ProcessIssuesMap const& _issue
|
|||
JsonString message = JsonString::makeMessage(i.first.c_str(), getIssueDescription(i.first).c_str());
|
||||
JsonStringArray addresses;
|
||||
for(auto addr : i.second) {
|
||||
addresses.push_back(JsonString(addr));
|
||||
addresses.push_back(addr);
|
||||
}
|
||||
|
||||
message["addresses"] = addresses;
|
||||
|
@ -1692,8 +1709,9 @@ ACTOR Future<JsonString> layerStatusFetcher(Database cx, JsonStringArray *messag
|
|||
}
|
||||
|
||||
json.cleanOps();
|
||||
JsonString statusObj = JsonString( result );
|
||||
TraceEvent("LayerStatusFetcher").detail("Duration", now()-tStart).detail("StatusSize",statusObj.getLength());
|
||||
JsonString statusObj;
|
||||
statusObj.append(result);
|
||||
TraceEvent("LayerStatusFetcher").detail("Duration", now()-tStart).detail("StatusSize",statusObj.getFinalLength());
|
||||
return statusObj;
|
||||
}
|
||||
|
||||
|
@ -1800,7 +1818,7 @@ ACTOR Future<StatusReply> clusterGetStatus(
|
|||
JsonString message = JsonString::makeMessage("unreachable_processes", "The cluster has some unreachable processes.");
|
||||
JsonStringArray unreachableProcs;
|
||||
for (auto m : mergeUnreachable){
|
||||
unreachableProcs.push_back(JsonString("address", m));
|
||||
unreachableProcs.push_back(JsonString().append("address", m));
|
||||
}
|
||||
message["unreachable_processes"] = unreachableProcs;
|
||||
messages.push_back(message);
|
||||
|
@ -1931,9 +1949,10 @@ ACTOR Future<StatusReply> clusterGetStatus(
|
|||
}
|
||||
else {
|
||||
// Set layers status to { _valid: false, error: "configurationMissing"}
|
||||
JsonString jsonString("_valid", false);
|
||||
jsonString.append("_error", "configurationMissing");
|
||||
statusObj["layers"] = jsonString;
|
||||
JsonString layers;
|
||||
layers.append("_valid", false);
|
||||
layers.append("_error", "configurationMissing");
|
||||
statusObj["layers"] = layers;
|
||||
}
|
||||
|
||||
JsonString processStatus = wait(processStatusFetcher(db, workers, pMetrics, mMetrics, latestError, traceFileOpenErrors, programStarts, processIssues, storageServers, tLogs, cx, configuration, &status_incomplete_reasons));
|
||||
|
@ -1942,7 +1961,7 @@ ACTOR Future<StatusReply> clusterGetStatus(
|
|||
|
||||
JsonStringArray incompatibleConnectionsArray;
|
||||
for(auto it : incompatibleConnections) {
|
||||
incompatibleConnectionsArray.push_back(JsonString(it.toString()));
|
||||
incompatibleConnectionsArray.push_back(it.toString());
|
||||
}
|
||||
statusObj["incompatible_connections"] = incompatibleConnectionsArray;
|
||||
statusObj["datacenter_version_difference"] = datacenterVersionDifference;
|
||||
|
@ -1965,7 +1984,7 @@ ACTOR Future<StatusReply> clusterGetStatus(
|
|||
// Make a JSON array of all of the reasons in the status_incomplete_reasons set.
|
||||
JsonStringArray reasons;
|
||||
for (auto i : status_incomplete_reasons) {
|
||||
reasons.push_back(JsonString("description", i));
|
||||
reasons.push_back(JsonString().append("description", i));
|
||||
}
|
||||
incomplete_message["reasons"] = reasons;
|
||||
messages.push_back(incomplete_message);
|
||||
|
@ -1978,9 +1997,9 @@ ACTOR Future<StatusReply> clusterGetStatus(
|
|||
statusObj["cluster_controller_timestamp"] = clusterTime;
|
||||
}
|
||||
|
||||
TraceEvent("ClusterGetStatus").detail("Duration", timer()-tStart).detail("StatusSize",statusObj.getLength());
|
||||
TraceEvent("ClusterGetStatus").detail("Duration", timer()-tStart).detail("StatusSize",statusObj.getFinalLength());
|
||||
|
||||
return StatusReply(statusObj);
|
||||
return StatusReply(statusObj.getJson());
|
||||
} catch( Error&e ) {
|
||||
TraceEvent(SevError, "StatusError").error(e);
|
||||
throw;
|
||||
|
@ -1988,37 +2007,39 @@ ACTOR Future<StatusReply> clusterGetStatus(
|
|||
}
|
||||
|
||||
TEST_CASE("status/json/jsonstring") {
|
||||
JsonString jsonObj, testObj;
|
||||
JsonString jsonObj, testObj;
|
||||
JsonStringArray jsonArray;
|
||||
|
||||
JsonString jsonString1;
|
||||
JsonString jsonString1;
|
||||
jsonString1["_valid"] = false;
|
||||
jsonString1["error"] = "configurationMissing";
|
||||
|
||||
JsonString jsonString2("_valid", false);
|
||||
JsonString jsonString2;
|
||||
jsonString2.append("_valid", false);
|
||||
jsonString2["error"] = "configurationMissing";
|
||||
|
||||
JsonString jsonString3("error", "configurationMissing");
|
||||
JsonString jsonString3;
|
||||
jsonString3.append("error", "configurationMissing");
|
||||
jsonString3["_valid"] = false;
|
||||
|
||||
JsonString jsonString4("_valid", false);
|
||||
JsonString jsonString4;
|
||||
jsonString4.append("_valid", false);
|
||||
jsonString4.append("error", "configurationMissing");
|
||||
|
||||
std::cout << "Json: " << jsonString1.getJson() << std::endl;
|
||||
|
||||
// Validate equality
|
||||
ASSERT(jsonString1 == jsonString2);
|
||||
ASSERT(jsonString1 != jsonString3); // wrong order
|
||||
ASSERT(jsonString1.equals(jsonString4));
|
||||
ASSERT(jsonString4.equals(jsonString1));
|
||||
ASSERT(jsonString1.getJson() == jsonString2.getJson());
|
||||
ASSERT(jsonString1.getJson() != jsonString3.getJson()); // wrong order
|
||||
ASSERT(jsonString1.getJson() == jsonString4.getJson());
|
||||
|
||||
// Check empty
|
||||
ASSERT(!jsonString1.empty());
|
||||
ASSERT(JsonString().empty());
|
||||
ASSERT(!jsonString1.getJson().empty());
|
||||
ASSERT(JsonString().getJson().empty());
|
||||
|
||||
// Check clear
|
||||
jsonString1.clear();
|
||||
ASSERT(jsonString1.empty());
|
||||
ASSERT(jsonString1.getJson().empty());
|
||||
|
||||
// Set object
|
||||
jsonObj.clear();
|
||||
|
@ -2047,9 +2068,9 @@ TEST_CASE("status/json/jsonstring") {
|
|||
std::cout << "Json (complex array): " << jsonObj.getJson() << std::endl;
|
||||
|
||||
jsonArray.clear();
|
||||
jsonArray.push_back(JsonString("nissan"));
|
||||
jsonArray.push_back(JsonString("honda"));
|
||||
jsonArray.push_back(JsonString("bmw"));
|
||||
jsonArray.push_back("nissan");
|
||||
jsonArray.push_back("honda");
|
||||
jsonArray.push_back("bmw");
|
||||
jsonObj.clear();
|
||||
jsonObj.append(jsonArray);
|
||||
std::cout << "Array (simple array #1): " << jsonObj.getJson() << std::endl;
|
||||
|
|
|
@ -43,8 +43,7 @@ struct StatusWorkload : TestWorkload {
|
|||
requestsPerSecond = getOption(options, LiteralStringRef("requestsPerSecond"), 0.5);
|
||||
auto statusSchemaStr = getOption(options, LiteralStringRef("schema"), JSONSchemas::statusSchema);
|
||||
if (statusSchemaStr.size()) {
|
||||
json_spirit::mValue schema;
|
||||
json_spirit::read_string( statusSchemaStr.toString(), schema );
|
||||
json_spirit::mValue schema = readJSONStrictly(statusSchemaStr.toString());
|
||||
parsedSchema = schema.get_obj();
|
||||
|
||||
// This is sort of a hack, but generate code coverage *requirements* for everything in schema
|
||||
|
@ -142,12 +141,10 @@ struct StatusWorkload : TestWorkload {
|
|||
WorkloadFactory<StatusWorkload> StatusWorkloadFactory("Status");
|
||||
|
||||
TEST_CASE("fdbserver/status/schema/basic") {
|
||||
json_spirit::mValue schema;
|
||||
json_spirit::read_string( std::string("{\"apple\":3,\"banana\":\"foo\",\"sub\":{\"thing\":true},\"arr\":[{\"a\":1,\"b\":2}],\"en\":{\"$enum\":[\"foo\",\"bar\"]},\"mapped\":{\"$map\":{\"x\":true}}"), schema );
|
||||
json_spirit::mValue schema = readJSONStrictly("{\"apple\":3,\"banana\":\"foo\",\"sub\":{\"thing\":true},\"arr\":[{\"a\":1,\"b\":2}],\"en\":{\"$enum\":[\"foo\",\"bar\"]},\"mapped\":{\"$map\":{\"x\":true}}");
|
||||
|
||||
auto check = [&schema](bool expect_ok, std::string t) {
|
||||
json_spirit::mValue test;
|
||||
json_spirit::read_string( t, test );
|
||||
json_spirit::mValue test = readJSONStrictly(t);
|
||||
TraceEvent("SchemaMatch").detail("Schema", json_spirit::write_string(schema)).detail("Value", json_spirit::write_string(test)).detail("Expect", expect_ok);
|
||||
std::string errorStr;
|
||||
ASSERT( expect_ok == schemaMatch(schema.get_obj(), test.get_obj(), errorStr, expect_ok ? SevError : SevInfo, true) );
|
||||
|
|
|
@ -192,6 +192,8 @@ ERROR( task_invalid_version, 2381, "Invalid task version")
|
|||
ERROR( task_interrupted, 2382, "Task execution stopped due to timeout, abort, or completion by another worker")
|
||||
|
||||
ERROR( key_not_found, 2400, "Expected key is missing")
|
||||
ERROR( json_malformed, 2401, "JSON string was malformed")
|
||||
ERROR( json_eof_expected, 2402, "JSON string did not terminate where expected")
|
||||
|
||||
// 4xxx Internal errors (those that should be generated only by bugs) are decimal 4xxx
|
||||
ERROR( unknown_error, 4000, "An unknown error occurred" ) // C++ exception not of type Error
|
||||
|
|
Loading…
Reference in New Issue