foundationdb/fdbclient/Status.h

397 lines
14 KiB
C++

/*
* Status.h
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FDBCLIENT_STATUS_H
#define FDBCLIENT_STATUS_H
#include "json_spirit/json_spirit_writer_template.h"
#include "json_spirit/json_spirit_reader_template.h"
struct StatusObject : json_spirit::mObject {
typedef json_spirit::mObject Map;
typedef json_spirit::mArray Array;
StatusObject() {}
StatusObject(json_spirit::mObject const& o) : json_spirit::mObject(o) {}
};
template <class Ar>
void load(Ar& ar, StatusObject& statusObj) {
std::string value;
int32_t length;
ar >> length;
value.resize(length);
ar.serializeBytes( &value[0], (int)value.length() );
json_spirit::mValue mv;
json_spirit::read_string( value, mv );
statusObj = StatusObject(mv.get_obj());
ASSERT( ar.protocolVersion() != 0 );
}
template <class Ar>
void save(Ar& ar, StatusObject const& statusObj) {
std::string value = json_spirit::write_string(json_spirit::mValue(statusObj));
ar << (int32_t)value.length();
ar.serializeBytes( (void*)&value[0], (int)value.length() );
ASSERT( ar.protocolVersion() != 0 );
}
struct StatusArray : json_spirit::mArray {
StatusArray() {}
StatusArray( json_spirit::mArray const& o ) : json_spirit::mArray(o.begin(), o.end()) {}
};
struct StatusValue : json_spirit::mValue {
StatusValue() {}
StatusValue(json_spirit::mValue const& o) : json_spirit::mValue(o) {}
};
static StatusObject makeMessage(const char *name, const char *description) {
StatusObject out;
out["name"] = name;
out["description"] = description;
return out;
}
// JSONDoc is a convenient reader/writer class for manipulating JSON documents using "paths".
// Access is done using a "path", which is a string of dot-separated
// substrings representing representing successively deeper keys found in nested
// JSON objects within the top level object
//
// Most methods are read-only with respect to the source JSON object.
// The only modifying methods are create(), put(), subDoc(), and mergeInto()
//
// JSONDoc maintains some state which is the JSON value that was found during the most recent
// *successful* path lookup.
//
// Examples:
// StatusObjectReader r(some_obj);
//
// // See if JSON doc path a.b.c exists
// bool exists = r.has("a.b.c");
//
// // See if JSON doc path a.b.c exists, if it does then assign value to x. Throws if path exists but T is not compatible.
// T x;
// bool exists = r.has("a.b.c", x);
//
// // This way you can chain things like this:
// bool is_two = r.has("a.b.c", x) && x == 2;
//
// // Alternatively, you can avoid the temp var by making use of the last() method which returns a reference
// // to the JSON value at the last successfully found path that has() has seen.
// bool is_int = r.has("a.b.c") && r.last().type == json_spirit::int_type;
// bool is_two = r.has("a.b.c") && r.last().get_int() == 2;
//
// // The familiar at() method also exists but now supports the same path concept.
// // It will throw in the same circumstances as the original method
// int x = r.at("a.b.c").get_int();
//
// // If you wish to access an element with the dot character within its name (e.g., "hostname.example.com"),
// // you can do so by setting the "split" flag to false in either the "has" or "get" methods. The example
// // below will look for the key "hostname.example.com" as a subkey of the path "a.b.c" (or, more
// // precisely, it will look to see if r.has("a").has("b").has("c").has("hostname.example.com", false)).
// bool exists = r.has("a.b.c").has("hostname.example.com", false);
//
// // And the familiar operator[] interface exists as well, however only as a synonym for at()
// // because this class is only for reading. Using operator [] will not auto-create null things.
// // The following would throw if a.b.c did not exist, or if it was not an int.
// int x = r["a.b.c"].get_int();
struct JSONDoc {
JSONDoc() : pObj(NULL) {}
// Construction from const json_spirit::mObject, trivial and will never throw.
// Resulting JSONDoc will not allow modifications.
JSONDoc(const json_spirit::mObject &o) : pObj(&o), wpObj(NULL) {}
// Construction from json_spirit::mObject. Allows modifications.
JSONDoc(json_spirit::mObject &o) : pObj(&o), wpObj(&o) {}
// Construction from const json_spirit::mValue (which is a Variant type) which will try to
// convert it to an mObject. This will throw if that fails, just as it would
// if the caller called get_obj() itself and used the previous constructor instead.
JSONDoc(const json_spirit::mValue &v) : pObj(&v.get_obj()), wpObj(NULL) {}
// Construction from non-const json_spirit::mValue - will convert the mValue to
// an object if it isn't already and then attach to it.
JSONDoc(json_spirit::mValue &v) {
if(v.type() != json_spirit::obj_type)
v = json_spirit::mObject();
wpObj = &v.get_obj();
pObj = wpObj;
}
// Returns whether or not a "path" exists.
// Returns true if all elements along path exist
// Returns false if any elements along the path are MISSING
// Will throw if a non-terminating path element exists BUT is not a JSON Object.
// If the "split" flag is set to "false", then this skips the splitting of a
// path into on the "dot" character.
// When a path is found, pLast is updated.
bool has(std::string path, bool split=true) {
if (pObj == NULL)
return false;
if (path.empty())
return false;
size_t start = 0;
const json_spirit::mValue *curVal = NULL;
while (start < path.size())
{
// If a path segment is found then curVal must be an object
size_t dot;
if (split) {
dot = path.find_first_of('.', start);
if (dot == std::string::npos)
dot = path.size();
} else {
dot = path.size();
}
std::string key = path.substr(start, dot - start);
// Get pointer to the current Object that the key has to be in
// This will throw if the value is not an Object
const json_spirit::mObject *curObj = curVal ? &curVal->get_obj() : pObj;
// Make sure key exists, if not then return false
if (!curObj->count(key))
return false;
// Advance curVal
curVal = &curObj->at(key);
// Advance start position in path
start = dot + 1;
}
pLast = curVal;
return true;
}
// Creates the given path (forcing Objects to exist along its depth, replacing whatever else might have been there)
// and returns a reference to the Value at that location.
json_spirit::mValue & create(std::string path, bool split=true) {
if (wpObj == NULL || path.empty())
throw std::runtime_error("JSON Object not writable or bad JSON path");
size_t start = 0;
json_spirit::mValue *curVal = nullptr;
while (start < path.size())
{
// Get next path segment name
size_t dot;
if (split) {
dot = path.find_first_of('.', start);
if (dot == std::string::npos)
dot = path.size();
} else {
dot = path.size();
}
std::string key = path.substr(start, dot - start);
if(key.empty())
throw std::runtime_error("invalid JSON path");
// Get/create pointer to the current Object that the key has to be in
// If curVal is defined then force it to be an Object
json_spirit::mObject *curObj;
if(curVal != nullptr) {
if(curVal->type() != json_spirit::obj_type)
*curVal = json_spirit::mObject();
curObj = &curVal->get_obj();
}
else // Otherwise start with the object *this is writing to
curObj = wpObj;
// Make sure key exists, if not then return false
if (!curObj->count(key))
(*curObj)[key] = json_spirit::mValue();
// Advance curVal
curVal = &((*curObj)[key]);
// Advance start position in path
start = dot + 1;
}
return *curVal;
}
// Creates the path given, puts a value at it, and returns a reference to the value
template<typename T>
T & put(std::string path, const T & value, bool split=true) {
json_spirit::mValue &v = create(path, split);
v = value;
return v.get_value<T>();
}
// Ensures that a an Object exists at path and returns a JSONDoc that writes to it.
JSONDoc subDoc(std::string path, bool split=true) {
json_spirit::mValue &v = create(path, split);
if(v.type() != json_spirit::obj_type)
v = json_spirit::mObject();
return JSONDoc(v.get_obj());
}
// Apply a merge operation to two values. Works for int, double, and string
template <typename T>
static json_spirit::mObject mergeOperator(const std::string &op, const json_spirit::mObject &op_a, const json_spirit::mObject &op_b, T const &a, T const &b) {
if(op == "$max")
return {{op, std::max<T>(a, b)}};
if(op == "$min")
return {{op, std::min<T>(a, b)}};
if(op == "$sum")
return {{op, a + b}};
throw std::exception();
}
// This is just a convenience function to make calling mergeOperator look cleaner
template <typename T>
static json_spirit::mObject mergeOperatorWrapper(const std::string &op, const json_spirit::mObject &op_a, const json_spirit::mObject &op_b, const json_spirit::mValue &a, const json_spirit::mValue &b) {
return mergeOperator<T>(op, op_a, op_b, a.get_value<T>(), b.get_value<T>());
}
static inline std::string getOperator(const json_spirit::mObject &obj) {
for(auto &k : obj)
if(!k.first.empty() && k.first[0] == '$')
return k.first;
return std::string();
}
// Merge src into dest, applying merge operators
static void mergeInto(json_spirit::mObject &dst, const json_spirit::mObject &src);
static void mergeValueInto(json_spirit::mValue &d, const json_spirit::mValue &s);
// Remove any merge operators that never met any mates.
static void cleanOps(json_spirit::mObject &obj);
void cleanOps() {
if(wpObj == nullptr)
throw std::runtime_error("JSON Object not writable");
return cleanOps(*wpObj);
}
void absorb(const JSONDoc &doc) {
if(wpObj == nullptr)
throw std::runtime_error("JSON Object not writable");
if(doc.pObj == nullptr)
throw std::runtime_error("JSON Object not readable");
mergeInto(*wpObj, *doc.pObj);
}
// Returns whether or not a "path" exists.
// Returns true if all elements along path exist
// Returns false if any elements along the path are MISSING
// Sets out to the value of the thing that path refers to
// Will throw if a non-terminating path element exists BUT is not a JSON Object.
// Will throw if all elements along path exists but T is an incompatible type
template <typename T> bool get(const std::string path, T &out, bool split=true) {
bool r = has(path, split);
if (r)
out = pLast->get_value<T>();
return r;
}
// For convenience, wraps get() in a try/catch and returns false UNLESS the path existed and was a compatible type.
template <typename T> bool tryGet(const std::string path, T &out, bool split=true) {
try { return get(path, out, split); } catch(...) {}
return false;
}
const json_spirit::mValue & at(const std::string path, bool split=true) {
if (has(path, split))
return last();
throw std::runtime_error("JSON path doesn't exist");
}
const json_spirit::mValue & operator[](const std::string path) {
return at(path);
}
const json_spirit::mValue & last() const { return *pLast; }
bool valid() const { return pObj != NULL; }
const json_spirit::mObject & obj() {
// This dummy object is necessary to make working with obj() easier when this does not currently
// point to a valid mObject. valid() can be called to explicitly check for this scenario, but
// calling obj() at least will not seg fault and instead return a const reference to an empty mObject.
// This is very useful when iterating using obj() to access the underlying mObject.
static const json_spirit::mObject dummy;
return pObj ? *pObj : dummy;
}
// Return reference to writeable underlying mObject but only if *this was initialized with a writeable value or object
json_spirit::mObject & wobj() {
ASSERT(wpObj != nullptr);
return *wpObj;
}
// This is the version used to represent 'now' for use by the $expires operator.
// By default, nothing will expire and it is up to the user of JSONDoc to update this value if
// it is intended to be used.
// This is slightly hackish but otherwise the JSON merge functions would require a Transaction.
static uint64_t expires_reference_version;
private:
const json_spirit::mObject *pObj;
// Writeable pointer to the same object. Will be NULL if initialized from a const object.
json_spirit::mObject *wpObj;
const json_spirit::mValue *pLast;
};
// Typedef to cover older code that was written when this class was only a reader and called StatusObjectReader
typedef JSONDoc StatusObjectReader;
// Template specialization for get<JSONDoc> because is convenient to get() an
// element from an object directly into a JSONDoc to have a handle to that sub-doc.
template <> inline bool JSONDoc::get<JSONDoc>(const std::string path, StatusObjectReader &out, bool split) {
bool r = has(path, split);
if (r)
out = pLast->get_obj();
return r;
}
// Takes an object by reference so make usage look clean and avoid the client doing object["messages"] which will create the key.
static bool findMessagesByName(StatusObjectReader object, std::set<std::string> to_find) {
if (!object.has("messages") || object.last().type() != json_spirit::array_type)
return false;
StatusObject::Array const &messages = object.last().get_array();
// Iterate over message array and look up each name in to_find.
// Not using StatusObjectReader here because it would throw if an i in messages is NOT an mObject
for (auto i : messages) {
// Since we are looking for a positive match, any exceptions thrown when trying to read
// the object in the messages array will be perceived as not-a-match and therefore ignored.
try
{
if (to_find.count(i.get_obj().at("name").get_str()))
return true;
}
catch (std::exception &)
{
}
}
return false;
}
#endif