From: Jesse F. Williamson <1643380+chardan@users.noreply.github.com> Date: Sat, 4 Jan 2025 01:41:24 +0000 (-0800) Subject: Consolidate codecs; views, buffer::list, etc.. X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=08d98617bf32808634e22dead0e6bc3a1c44a4a3;p=ceph.git Consolidate codecs; views, buffer::list, etc.. Move JSON encoding from source file into inline header functions; reorganize. Consolidate mostly-similar implementations with templates. Allow string_view, buffer::list to ceph_json parse(); drive-by cleanups. Assorted extensions (string_view for most search functions, etc.) Signed-off-by: Jesse F. Williamson --- diff --git a/src/common/ceph_json.cc b/src/common/ceph_json.cc index 1b54d6f71ce..af29226a87a 100644 --- a/src/common/ceph_json.cc +++ b/src/common/ceph_json.cc @@ -6,51 +6,28 @@ #include #include -// Enable boost.json's header-only mode ("https://github.com/boostorg/json?tab=readme-ov-file#header-only"): +/* Enable boost.json's header-only mode: + (see: "https://github.com/boostorg/json?tab=readme-ov-file#header-only"): */ #include #include +#include #include using std::ifstream; -using std::pair; using std::ostream; -using std::string; -using std::vector; - -using ceph::bufferlist; -using ceph::Formatter; - -#define dout_subsys ceph_subsys_rgw - -static JSONFormattable default_formattable; -void encode_json(const char *name, const JSONObj::data_val& v, Formatter *f) -{ - if (v.quoted) { - encode_json(name, v.str, f); - } else { - f->dump_format_unquoted(name, "%s", v.str.c_str()); - } -} +using std::begin; +using std::end; -void JSONObjIter::set(const JSONObjIter::map_iter_t &_cur, const JSONObjIter::map_iter_t &_last) -{ - cur = _cur; - last = _last; -} +using std::pair; +using std::vector; -void JSONObjIter::operator++() -{ - if (cur != last) - ++cur; -} +using std::string; +using std::string_view; -// IMPORTANT: The returned pointer is intended as NON-OWNING (i.e. the JSONObjIter is responsible for it): -JSONObj *JSONObjIter::operator*() -{ - return cur->second.get(); -} +using ceph::bufferlist; +using ceph::Formatter; // does not work, FIXME ostream& operator<<(ostream &out, const JSONObj &obj) { @@ -58,68 +35,6 @@ ostream& operator<<(ostream &out, const JSONObj &obj) { return out; } -void JSONObj::add_child(string el, JSONObj *obj) -{ - auto p = std::unique_ptr(obj); - - children.insert({el, std::move(p)}); -} - -bool JSONObj::get_attr(string name, data_val& attr) -{ - auto iter = attr_map.find(name); - if (iter == attr_map.end()) - return false; - attr = iter->second; - return true; -} - -JSONObjIter JSONObj::find(const string& name) -{ - JSONObjIter iter; - auto first = children.find(name); - if (first != children.end()) { - auto last = children.upper_bound(name); - iter.set(first, last); - } - return iter; -} - -JSONObjIter JSONObj::find_first() -{ - JSONObjIter iter; - iter.set(children.begin(), children.end()); - return iter; -} - -JSONObjIter JSONObj::find_first(const string& name) -{ - JSONObjIter iter; - auto first = children.find(name); - iter.set(first, children.end()); - return iter; -} - -JSONObj *JSONObj::find_obj(const string& name) -{ - JSONObjIter iter = find(name); - if (iter.end()) - return NULL; - - return *iter; -} - -bool JSONObj::get_data(const string& key, data_val *dest) -{ - JSONObj *obj = find_obj(key); - if (!obj) - return false; - - *dest = obj->get_data_val(); - - return true; -} - /* accepts a JSON Array or JSON Object contained in * a Boost.JSON value, v, and creates a Ceph JSONObj for each * child contained in v @@ -127,106 +42,68 @@ bool JSONObj::get_data(const string& key, data_val *dest) void JSONObj::handle_value(boost::json::value v) { if (auto op = v.if_object()) { - - for (const auto& kvp : *op) - { - JSONObj *child = new JSONObj; - - child->init(this, kvp.value(), kvp.key()); - - add_child(kvp.key(), child); + for (const auto& kvp : *op) { + auto child = std::make_unique(this, kvp.key(), kvp.value()); + children.insert(std::pair { kvp.key(), std::move(child) }); } return; } if (auto ap = v.if_array()) { - for (const auto& kvp : *ap) - { - JSONObj *child = new JSONObj; - child->init(this, kvp, ""); - - add_child(child->get_name(), child); + for (const auto& kvp : *ap) { + auto child = std::make_unique(this, "", kvp); + children.insert(std::pair { child->get_name(), std::move(child) }); } } // unknown type is not-an-error } -void JSONObj::init(JSONObj *parent_node, boost::json::value data_in, std::string name_in) +vector JSONObj::get_array_elements() { - parent = parent_node; - data = data_in; - name = name_in; + vector elements; - handle_value(data_in); + if (!data.is_array()) + return elements; - if (auto vp = data_in.if_string()) - val.set(*vp, true); - else { - val.set(boost::json::serialize(data_in), false); - } + std::ranges::for_each(data.as_array(), [&elements](const auto& i) { + elements.emplace_back(boost::json::serialize(i)); + }); - attr_map.insert({name, val}); + return elements; } -JSONObj *JSONObj::get_parent() +// parse the complete internal json_buffer +bool JSONParser::parse() { - return parent; -} + if (!parse_json(json_buffer, data)) + return false; -bool JSONObj::is_object() -{ - return data.is_object(); -} + handle_value(data); -bool JSONObj::is_array() -{ - return data.is_array(); + return true; } -vector JSONObj::get_array_elements() +// parse the internal json_buffer up to len +bool JSONParser::parse(int len) { - vector elements; - - boost::json::array temp_array; - - if (auto ap = data.if_array()) - temp_array = *ap; - - auto array_size = temp_array.size(); - - if (array_size > 0) { - for (boost::json::array::size_type i = 0; i < array_size; i++) { - boost::json::value temp_value = temp_array[i]; - string temp_string; - temp_string = boost::json::serialize(temp_value); - elements.push_back(temp_string); - } - } + if (!parse_json(std::string_view { std::begin(json_buffer), len + std::begin(json_buffer) }, data)) + return false; - return elements; -} + handle_value(data); -void JSONParser::handle_data(const char *s, int len) -{ - json_buffer.append(s, len); // check for problems with null termination FIXME - buf_len += len; + return true; } -// parse a supplied JSON fragment -bool JSONParser::parse(const char *buf_, int len) +// parse a supplied JSON fragment: +bool JSONParser::parse(std::string_view json_string_view) { - if (!buf_ || 0 >= len) { - return false; - } - - std::string_view json_string_view(buf_, len); + // The original implementation checked this, I'm not sure we have to but we're doing it for now: + if (json_string_view.empty()) + return false; - std::error_code ec; - data = boost::json::parse(json_string_view, ec); - - if(ec) + if (!parse_json(json_string_view, data)) return false; // recursively evaluate the result: @@ -243,7 +120,8 @@ bool JSONParser::parse(const char *buf_, int len) // For any other kind of value: std::string s = boost::json::serialize(data); - if (s.size() == static_cast(len)) { // entire string was read + // Was the entire string read? + if (s.size() == static_cast(json_string_view.length())) { val.set(s, false); return true; } @@ -252,35 +130,6 @@ bool JSONParser::parse(const char *buf_, int len) return false; } -// parse the internal json_buffer up to len -bool JSONParser::parse(int len) -{ - std::string_view json_string(std::begin(json_buffer), len + std::begin(json_buffer)); - - std::error_code ec; - if(data = boost::json::parse(json_string, ec); ec) - return false; - - handle_value(data); - - return true; -} - -// parse the complete internal json_buffer -bool JSONParser::parse() -{ - std::error_code ec; - - data = boost::json::parse(json_buffer, ec); - - if(ec) - return false; - - handle_value(data); - - return true; -} - // parse a supplied ifstream: bool JSONParser::parse(const char *file_name) { @@ -289,7 +138,7 @@ bool JSONParser::parse(const char *file_name) std::error_code ec; data = boost::json::parse(is, ec); - if(ec) + if (ec) return false; handle_value(data); @@ -297,418 +146,13 @@ bool JSONParser::parse(const char *file_name) return true; } - -void decode_json_obj(long& val, JSONObj *obj) -{ - string s = obj->get_data(); - const char *start = s.c_str(); - char *p; - - errno = 0; - val = strtol(start, &p, 10); - - /* Check for various possible errors */ - - if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) || - (errno != 0 && val == 0)) { - throw JSONDecoder::err("failed to parse number"); - } - - if (p == start) { - throw JSONDecoder::err("failed to parse number"); - } - - while (*p != '\0') { - if (!isspace(*p)) { - throw JSONDecoder::err("failed to parse number"); - } - p++; - } -} - -void decode_json_obj(unsigned long& val, JSONObj *obj) -{ - string s = obj->get_data(); - const char *start = s.c_str(); - char *p; - - errno = 0; - val = strtoul(start, &p, 10); - - /* Check for various possible errors */ - - if ((errno == ERANGE && val == ULONG_MAX) || - (errno != 0 && val == 0)) { - throw JSONDecoder::err("failed to number"); - } - - if (p == start) { - throw JSONDecoder::err("failed to parse number"); - } - - while (*p != '\0') { - if (!isspace(*p)) { - throw JSONDecoder::err("failed to parse number"); - } - p++; - } -} - -void decode_json_obj(long long& val, JSONObj *obj) -{ - string s = obj->get_data(); - const char *start = s.c_str(); - char *p; - - errno = 0; - val = strtoll(start, &p, 10); - - /* Check for various possible errors */ - - if ((errno == ERANGE && (val == LLONG_MAX || val == LLONG_MIN)) || - (errno != 0 && val == 0)) { - throw JSONDecoder::err("failed to parse number"); - } - - if (p == start) { - throw JSONDecoder::err("failed to parse number"); - } - - while (*p != '\0') { - if (!isspace(*p)) { - throw JSONDecoder::err("failed to parse number"); - } - p++; - } -} - -void decode_json_obj(unsigned long long& val, JSONObj *obj) -{ - string s = obj->get_data(); - const char *start = s.c_str(); - char *p; - - errno = 0; - val = strtoull(start, &p, 10); - - /* Check for various possible errors */ - - if ((errno == ERANGE && val == ULLONG_MAX) || - (errno != 0 && val == 0)) { - throw JSONDecoder::err("failed to number"); - } - - if (p == start) { - throw JSONDecoder::err("failed to parse number"); - } - - while (*p != '\0') { - if (!isspace(*p)) { - throw JSONDecoder::err("failed to parse number"); - } - p++; - } -} - -void decode_json_obj(int& val, JSONObj *obj) -{ - long l; - decode_json_obj(l, obj); -#if LONG_MAX > INT_MAX - if (l > INT_MAX || l < INT_MIN) { - throw JSONDecoder::err("integer out of range"); - } -#endif - - val = (int)l; -} - -void decode_json_obj(unsigned& val, JSONObj *obj) -{ - unsigned long l; - decode_json_obj(l, obj); -#if ULONG_MAX > UINT_MAX - if (l > UINT_MAX) { - throw JSONDecoder::err("unsigned integer out of range"); - } -#endif - - val = (unsigned)l; -} - -void decode_json_obj(bool& val, JSONObj *obj) -{ - string s = obj->get_data(); - if (strcasecmp(s.c_str(), "true") == 0) { - val = true; - return; - } - if (strcasecmp(s.c_str(), "false") == 0) { - val = false; - return; - } - int i; - decode_json_obj(i, obj); - val = (bool)i; -} - -void decode_json_obj(bufferlist& val, JSONObj *obj) -{ - string s = obj->get_data(); - - bufferlist bl; - bl.append(s.c_str(), s.size()); - try { - val.decode_base64(bl); - } catch (ceph::buffer::error& err) { - throw JSONDecoder::err("failed to decode base64"); - } -} - -void decode_json_obj(utime_t& val, JSONObj *obj) -{ - string s = obj->get_data(); - uint64_t epoch; - uint64_t nsec; - int r = utime_t::parse_date(s, &epoch, &nsec); - if (r == 0) { - val = utime_t(epoch, nsec); - } else { - throw JSONDecoder::err("failed to decode utime_t"); - } -} - -void decode_json_obj(ceph::real_time& val, JSONObj *obj) -{ - const std::string& s = obj->get_data(); - uint64_t epoch; - uint64_t nsec; - int r = utime_t::parse_date(s, &epoch, &nsec); - if (r == 0) { - using namespace std::chrono; - val = real_time{seconds(epoch) + nanoseconds(nsec)}; - } else { - throw JSONDecoder::err("failed to decode real_time"); - } -} - -void decode_json_obj(ceph::coarse_real_time& val, JSONObj *obj) -{ - const std::string& s = obj->get_data(); - uint64_t epoch; - uint64_t nsec; - int r = utime_t::parse_date(s, &epoch, &nsec); - if (r == 0) { - using namespace std::chrono; - val = coarse_real_time{seconds(epoch) + nanoseconds(nsec)}; - } else { - throw JSONDecoder::err("failed to decode coarse_real_time"); - } -} - -void decode_json_obj(ceph_dir_layout& i, JSONObj *obj){ - - unsigned tmp; - JSONDecoder::decode_json("dir_hash", tmp, obj, true); - i.dl_dir_hash = tmp; - JSONDecoder::decode_json("unused1", tmp, obj, true); - i.dl_unused1 = tmp; - JSONDecoder::decode_json("unused2", tmp, obj, true); - i.dl_unused2 = tmp; - JSONDecoder::decode_json("unused3", tmp, obj, true); - i.dl_unused3 = tmp; -} - -void encode_json(const char *name, std::string_view val, Formatter *f) -{ - f->dump_string(name, val); -} - -void encode_json(const char *name, const string& val, Formatter *f) -{ - f->dump_string(name, val); -} - -void encode_json(const char *name, const char *val, Formatter *f) -{ - f->dump_string(name, val); -} - -void encode_json(const char *name, bool val, Formatter *f) -{ - f->dump_bool(name, val); -} - -void encode_json(const char *name, int val, Formatter *f) -{ - f->dump_int(name, val); -} - -void encode_json(const char *name, long val, Formatter *f) -{ - f->dump_int(name, val); -} - -void encode_json(const char *name, unsigned val, Formatter *f) -{ - f->dump_unsigned(name, val); -} - -void encode_json(const char *name, unsigned long val, Formatter *f) -{ - f->dump_unsigned(name, val); -} - -void encode_json(const char *name, unsigned long long val, Formatter *f) -{ - f->dump_unsigned(name, val); -} - -void encode_json(const char *name, long long val, Formatter *f) -{ - f->dump_int(name, val); -} - -void encode_json(const char *name, const utime_t& val, Formatter *f) -{ - val.gmtime(f->dump_stream(name)); -} - -void encode_json(const char *name, const ceph::real_time& val, Formatter *f) -{ - encode_json(name, utime_t{val}, f); -} - -void encode_json(const char *name, const ceph::coarse_real_time& val, Formatter *f) -{ - encode_json(name, utime_t{val}, f); -} - -void encode_json(const char *name, const bufferlist& bl, Formatter *f) -{ - /* need to copy data from bl, as it is const bufferlist */ - bufferlist src = bl; - - bufferlist b64; - src.encode_base64(b64); - - string s(b64.c_str(), b64.length()); - - encode_json(name, s, f); -} - - - /* JSONFormattable */ -const JSONFormattable& JSONFormattable::operator[](const string& name) const -{ - auto i = obj.find(name); - if (i == obj.end()) { - return default_formattable; - } - return i->second; -} - -const JSONFormattable& JSONFormattable::operator[](size_t index) const -{ - if (index >= arr.size()) { - return default_formattable; - } - return arr[index]; -} - -JSONFormattable& JSONFormattable::operator[](const string& name) -{ - auto i = obj.find(name); - if (i == obj.end()) { - return default_formattable; - } - return i->second; -} - -JSONFormattable& JSONFormattable::operator[](size_t index) -{ - if (index >= arr.size()) { - return default_formattable; - } - return arr[index]; -} - -bool JSONFormattable::exists(const string& name) const -{ - auto i = obj.find(name); - return (i != obj.end()); -} - -bool JSONFormattable::exists(size_t index) const -{ - return (index < arr.size()); -} - -bool JSONFormattable::find(const string& name, string *val) const -{ - auto i = obj.find(name); - if (i == obj.end()) { - return false; - } - *val = i->second.val(); - return true; -} - -int JSONFormattable::val_int() const { - return atoi(value.str.c_str()); -} - -long JSONFormattable::val_long() const { - return atol(value.str.c_str()); -} - -long long JSONFormattable::val_long_long() const { - return atoll(value.str.c_str()); -} - bool JSONFormattable::val_bool() const { return (boost::iequals(value.str, "true") || + boost::iequals(value.str, "1") || boost::iequals(value.str, "on") || - boost::iequals(value.str, "yes") || - boost::iequals(value.str, "1")); -} - -string JSONFormattable::def(const string& def_val) const { - if (type == FMT_NONE) { - return def_val; - } - return val(); -} - -int JSONFormattable::def(int def_val) const { - if (type == FMT_NONE) { - return def_val; - } - return val_int(); -} - -bool JSONFormattable::def(bool def_val) const { - if (type == FMT_NONE) { - return def_val; - } - return val_bool(); -} - -string JSONFormattable::get(const string& name, const string& def_val) const -{ - return (*this)[name].def(def_val); -} - -int JSONFormattable::get_int(const string& name, int def_val) const -{ - return (*this)[name].def(def_val); -} - -bool JSONFormattable::get_bool(const string& name, bool def_val) const -{ - return (*this)[name].def(def_val); + boost::iequals(value.str, "yes")); } struct field_entity { @@ -717,7 +161,8 @@ struct field_entity { int index{0}; /* if array */ bool append{false}; - field_entity() {} + field_entity() = default; + explicit field_entity(std::string_view sv) : is_obj(true), name(sv) {} explicit field_entity(const string& n) : is_obj(true), name(n) {} explicit field_entity(int i) : is_obj(false), index(i) {} }; @@ -732,12 +177,14 @@ static int parse_entity(const string& s, vector *result) if (ofs != 0) { return -EINVAL; } - result->push_back(field_entity(s)); + result->emplace_back(field_entity(s)); return 0; } if (next_arr > ofs) { - string field = s.substr(ofs, next_arr - ofs); - result->push_back(field_entity(field)); + + auto field = string_view(ofs + begin(s), next_arr - ofs + begin(s)); + + result->emplace_back(field_entity(field)); ofs = next_arr; } size_t end_arr = s.find(']', next_arr + 1); @@ -745,16 +192,22 @@ static int parse_entity(const string& s, vector *result) return -EINVAL; } - string index_str = s.substr(next_arr + 1, end_arr - next_arr - 1); + auto index_str = string_view(s).substr(1 + next_arr, end_arr - next_arr - 1); ofs = end_arr + 1; if (!index_str.empty()) { - result->push_back(field_entity(atoi(index_str.c_str()))); + + int x; + if (auto [_, ec] = std::from_chars(begin(index_str), end(index_str), x); std::errc() == ec) + result->emplace_back(field_entity(x)); + else + throw std::invalid_argument(fmt::format("{}", index_str)); + } else { field_entity f; f.append = true; - result->push_back(f); + result->emplace_back(f); } } return 0; @@ -779,7 +232,7 @@ int JSONFormattable::set(const string& name, const string& val) JSONParser jp; - bool is_valid_json = jp.parse(val.c_str(), val.size()); + bool is_valid_json = jp.parse(val); for (const auto& i : tok) { vector v; @@ -914,36 +367,10 @@ void JSONFormattable::derive_from(const JSONFormattable& parent) } } -void encode_json(const char *name, const JSONFormattable& v, Formatter *f) -{ - v.encode_json(name, f); -} - -void JSONFormattable::encode_json(const char *name, Formatter *f) const -{ - switch (type) { - case JSONFormattable::FMT_VALUE: - ::encode_json(name, value, f); - break; - case JSONFormattable::FMT_ARRAY: - ::encode_json(name, arr, f); - break; - case JSONFormattable::FMT_OBJ: - f->open_object_section(name); - for (auto iter : obj) { - ::encode_json(iter.first.c_str(), iter.second, f); - } - f->close_section(); - break; - case JSONFormattable::FMT_NONE: - break; - } -} - bool JSONFormattable::handle_value(std::string_view name, std::string_view s, bool quoted) { JSONFormattable *new_val; if (cur_enc->is_array()) { - cur_enc->arr.push_back(JSONFormattable()); + cur_enc->arr.emplace_back(JSONFormattable()); new_val = &cur_enc->arr.back(); } else { cur_enc->set_type(JSONFormattable::FMT_OBJ); @@ -959,7 +386,7 @@ bool JSONFormattable::handle_open_section(std::string_view name, const char *ns, bool section_is_array) { if (cur_enc->is_array()) { - cur_enc->arr.push_back(JSONFormattable()); + cur_enc->arr.emplace_back(JSONFormattable()); cur_enc = &cur_enc->arr.back(); } else if (enc_stack.size() > 1) { /* only open a new section if already nested, @@ -967,7 +394,7 @@ bool JSONFormattable::handle_open_section(std::string_view name, */ cur_enc = &cur_enc->obj[string{name}]; } - enc_stack.push_back(cur_enc); + enc_stack.emplace_back(cur_enc); if (section_is_array) { cur_enc->set_type(JSONFormattable::FMT_ARRAY); diff --git a/src/common/ceph_json.h b/src/common/ceph_json.h index dcdc9b995fb..a52a602e036 100644 --- a/src/common/ceph_json.h +++ b/src/common/ceph_json.h @@ -1,56 +1,156 @@ #ifndef CEPH_JSON_H #define CEPH_JSON_H +#include + #include #include #include #include + #include -#include + +#include +#include + #include +#include + +#include +#include #include +#include + #include + #include #include +#include + #include #include +#include "include/buffer.h" + #include "common/ceph_time.h" +#include + #include "Formatter.h" +class utime_t; + class JSONObj; +class JSONFormattable; + +namespace ceph_json::detail { + +template +consteval bool is_any_of() +{ + return (std::is_same_v || ...); +} + +/* Note that std::is_integer<> will also pick up bool, which in our +case we need to count as a non-integer type as the JSON codecs treat +it differently ("https://en.cppreference.com/w/cpp/types/is_integral"). + +From what I can see in the extant code, it looks like the same rules +should be applied to char, etc., so I've also done so here: +*/ +template +concept json_integer = requires +{ + requires std::is_integral_v; + requires !std::is_same_v; + + requires !is_any_of(); +}; + +template +concept json_signed_integer = requires +{ + requires json_integer && std::signed_integral; +}; + +template +concept json_unsigned_integer = requires +{ + requires json_integer && std::unsigned_integral; +}; + +/* Distinguish between containers with a value that's an associative kv-pair (a mapped type) and +those which are a "single" value. Note that this is not the same as the AssociativeContainer +named concept, as the rule there is that the container is key-indexed, and it does not necessarily +have to be a pair (e.g. std::set<> is an AssociativeContainer). Similarly, for sequence types +we don't want to capture standard strings and the like, even if we otherwise could consider them +a value sequence: +*/ +template +concept json_mapped_kv_seq = requires +{ + typename ContainerT::key_type; + typename ContainerT::key_compare; + + typename ContainerT::value_type; + typename ContainerT::mapped_type; +}; + +template +concept json_val_seq = requires +{ + typename ContainerT::value_type; + + requires !json_mapped_kv_seq; + requires !std::convertible_to; +}; -class JSONObjIter { +} // namespace ceph_json - using map_iter_t = std::map>::iterator; +class JSONObjIter final { + + using map_iter_t = std::map, std::less<>>::iterator; map_iter_t cur; map_iter_t last; +private: + JSONObjIter(const JSONObjIter::map_iter_t& cur, const JSONObjIter::map_iter_t& end) + : cur(cur), + last(end) + {} + +public: + JSONObjIter() = default; + public: - void set(const JSONObjIter::map_iter_t &_cur, const JSONObjIter::map_iter_t &_end); + void set(const JSONObjIter::map_iter_t &_cur, const JSONObjIter::map_iter_t &_end) { + cur = _cur; + last = _end; + } - void operator++(); + void operator++() { if (cur != last) ++cur; } // IMPORTANT: The returned pointer is intended as NON-OWNING (i.e. JSONObjIter // is responsible for it): - JSONObj *operator*(); + JSONObj *operator*() { return cur->second.get(); } - bool end() const { - return (cur == last); - } + bool end() const { return (cur == last); } + +private: + friend JSONObj; }; class JSONObj { - using children_multimap_t = std::multimap>; - using children_multimap_value_type = typename children_multimap_t::value_type; - JSONObj *parent = nullptr; +protected: + using children_multimap_t = std::multimap, std::less<>>; + using children_multimap_value_type = typename children_multimap_t::value_type; + public: struct data_val { std::string str; @@ -71,41 +171,90 @@ protected: bool data_quoted{false}; - std::multimap> children; - - std::map attr_map; + children_multimap_t children; + std::map> attr_map; void handle_value(boost::json::value v); +protected: + /* Too bad that we lose the information from the error code, but it's actually already + junked anyway. */ + static bool parse_json(std::string_view input, boost::json::value& data_out) + { + std::error_code ec; + + data_out = boost::json::parse(input, ec, boost::json::storage_ptr(), + { .allow_invalid_utf8 = true }); + + return ec ? false : true; + } + public: + JSONObj() = default; + + JSONObj(JSONObj *parent_node, std::string_view name_in, boost::json::value data_in) + : parent { parent_node }, + name { name_in }, + data { data_in } + { + handle_value(data); + + if (auto vp = data_in.if_string()) + val.set(*vp, true); + else + val.set(boost::json::serialize(data), false); + } + virtual ~JSONObj() = default; public: - void init(JSONObj *parent_node, boost::json::value data_in, std::string name_in); + std::string& get_name() noexcept { return name; } + data_val& get_data_val() noexcept { return val; } - std::string& get_name() { return name; } - data_val& get_data_val() { return val; } + const std::string& get_data() const noexcept { return val.str; } - const std::string& get_data() { return val.str; } - bool get_data(const std::string& key, data_val *dest); + bool get_data(std::string_view key, data_val *dest) { + + JSONObj *obj = find_obj(key); + if (!obj) + return false; + + *dest = obj->get_data_val(); + + return true; + } - JSONObj *get_parent(); + JSONObj *get_parent() const noexcept { return parent; }; + + bool get_attr(std::string_view name, data_val& attr) { + if (auto i = attr_map.find(name); end(attr_map) != i) + return (attr = i->second), true; + + return false; + } + + JSONObjIter find(std::string_view name) { + return { children.find(name), children.upper_bound(name) }; + } - // Note: takes ownership of child: - void add_child(std::string el, JSONObj *child); + JSONObjIter find_first() { + return { children.begin(), children.end() }; + } - bool get_attr(std::string name, data_val& attr); + JSONObjIter find_first(std::string_view name) { + return { children.find(name), children.end() }; + } - JSONObjIter find(const std::string& name); - JSONObjIter find_first(); - JSONObjIter find_first(const std::string& name); - JSONObj *find_obj(const std::string& name); + JSONObj *find_obj(std::string_view name) { + JSONObjIter i = this->find(name); + return i.end() ? nullptr : *i; + } friend std::ostream& operator<<(std::ostream &out, const JSONObj &obj); // does not work, FIXME - bool is_array(); - bool is_object(); + bool is_array() const noexcept { return data.is_array(); } + bool is_object() const noexcept { return data.is_object(); } std::vector get_array_elements(); }; @@ -116,25 +265,38 @@ inline std::ostream& operator<<(std::ostream &out, const JSONObj::data_val& dv) return out; } -class JSONParser : public JSONObj +class JSONParser final : public JSONObj { int buf_len = 0; std::string json_buffer; public: - void handle_data(const char *s, int len); + ~JSONParser() override = default; - bool parse(const char *buf_, int len); - bool parse(int len); +public: + // operate on the internal buffer: bool parse(); + bool parse(int len); + + // operate on a string/stringlike range or object: + bool parse(std::string_view sv); + + bool parse(const char *buf_, int len) { + return buf_ ? + parse(std::string_view { buf_, static_cast(len) }) + : false; + } + + bool parse(ceph::buffer::list& bl) { return parse(std::string_view { bl.c_str(), bl.length() }); } + + // operate on a data file: bool parse(const char *file_name); - const char *get_json() { return json_buffer.c_str(); } +public: + const char *get_json() const noexcept{ return json_buffer.c_str(); } }; -void encode_json(const char *name, const JSONObj::data_val& v, ceph::Formatter *f); - -class JSONDecoder { +class JSONDecoder final { public: struct err : std::runtime_error { using runtime_error::runtime_error; @@ -143,29 +305,47 @@ public: JSONParser parser; JSONDecoder(ceph::buffer::list& bl) { - if (!parser.parse(bl.c_str(), bl.length())) { - std::cout << "JSONDecoder::err()" << std::endl; // JFW: do we still want this here? + if (!parser.parse(bl)) throw JSONDecoder::err("failed to parse JSON input"); - } } template - static bool decode_json(const char *name, T& val, JSONObj *obj, bool mandatory = false); + static bool decode_json(std::string_view name, T& val, JSONObj *obj, bool mandatory = false); template - static bool decode_json(const char *name, C& container, void (*cb)(C&, JSONObj *obj), JSONObj *obj, bool mandatory = false); + static bool decode_json(std::string_view name, C& container, void (*cb)(C&, JSONObj *obj), JSONObj *obj, bool mandatory = false); template - static void decode_json(const char *name, T& val, const T& default_val, JSONObj *obj); + static void decode_json(std::string_view name, T& val, const T& default_val, JSONObj *obj); template - static bool decode_json(const char *name, boost::optional& val, JSONObj *obj, bool mandatory = false); + static bool decode_json(std::string_view name, boost::optional& val, JSONObj *obj, bool mandatory = false); template - static bool decode_json(const char *name, std::optional& val, JSONObj *obj, bool mandatory = false); - + static bool decode_json(std::string_view name, std::optional& val, JSONObj *obj, bool mandatory = false); }; +template +requires ceph_json::detail::json_signed_integer || + ceph_json::detail::json_unsigned_integer +void decode_json_obj(IntegerT& val, JSONObj *obj) +try +{ + if constexpr (ceph_json::detail::json_signed_integer) + val = stol(obj->get_data()); + + if constexpr (ceph_json::detail::json_unsigned_integer) + val = stoul(obj->get_data()); +} +catch(const std::out_of_range& e) +{ + throw JSONDecoder::err(fmt::format("failed to parse number: {}", e.what())); +} +catch(const std::invalid_argument& e) +{ + throw JSONDecoder::err(fmt::format("failed to parse number: {}", e.what())); +} + template void decode_json_obj(T& val, JSONObj *obj) { @@ -177,183 +357,149 @@ inline void decode_json_obj(std::string& val, JSONObj *obj) val = obj->get_data(); } -static inline void decode_json_obj(JSONObj::data_val& val, JSONObj *obj) +inline void decode_json_obj(JSONObj::data_val& val, JSONObj *obj) { val = obj->get_data_val(); } -void decode_json_obj(unsigned long long& val, JSONObj *obj); -void decode_json_obj(long long& val, JSONObj *obj); -void decode_json_obj(unsigned long& val, JSONObj *obj); -void decode_json_obj(long& val, JSONObj *obj); -void decode_json_obj(unsigned& val, JSONObj *obj); -void decode_json_obj(int& val, JSONObj *obj); -void decode_json_obj(bool& val, JSONObj *obj); -void decode_json_obj(ceph::buffer::list& val, JSONObj *obj); -class utime_t; -void decode_json_obj(utime_t& val, JSONObj *obj); -void decode_json_obj(ceph_dir_layout& i, JSONObj *obj); - -void decode_json_obj(ceph::real_time& val, JSONObj *obj); -void decode_json_obj(ceph::coarse_real_time& val, JSONObj *obj); - -template -void decode_json_obj(std::list& l, JSONObj *obj) +inline void decode_json_obj(bool& val, JSONObj *obj) { - l.clear(); + std::string_view sv(obj->get_data()); - JSONObjIter iter = obj->find_first(); + if (boost::iequals(sv, "true")) + { + val = true; + return; + } - for (; !iter.end(); ++iter) { - T val; - JSONObj *o = *iter; - decode_json_obj(val, o); - l.push_back(val); + if (boost::iequals(sv, "false")) + { + val = false; + return; } + + // For 1, 0, anything else: + int i; + decode_json_obj(i, obj); + val = static_cast(i); } -template -void decode_json_obj(std::deque& l, JSONObj *obj) +inline void decode_json_obj(bufferlist& val, JSONObj *obj) { - l.clear(); + bufferlist bl; - JSONObjIter iter = obj->find_first(); + std::string_view sv = obj->get_data(); - for (; !iter.end(); ++iter) { - T val; - JSONObj *o = *iter; - decode_json_obj(val, o); - l.push_back(val); - } -} + bl.append(sv); -template -void decode_json_obj(std::set& l, JSONObj *obj) -{ - l.clear(); - - JSONObjIter iter = obj->find_first(); + try { + val.decode_base64(bl); - for (; !iter.end(); ++iter) { - T val; - JSONObj *o = *iter; - decode_json_obj(val, o); - l.insert(val); + } catch (ceph::buffer::error& err) { + throw JSONDecoder::err("failed to decode base64"); } } -template -void decode_json_obj(boost::container::flat_set& l, JSONObj *obj) +inline void decode_json_obj(utime_t& val, JSONObj *obj) { - l.clear(); - - JSONObjIter iter = obj->find_first(); - - for (; !iter.end(); ++iter) { - T val; - JSONObj *o = *iter; - decode_json_obj(val, o); - l.insert(val); + uint64_t epoch; + uint64_t nsec; + int r = utime_t::parse_date(obj->get_data(), &epoch, &nsec); + if (r == 0) { + val = utime_t(epoch, nsec); + } else { + throw JSONDecoder::err("failed to decode utime_t"); } } -template -void decode_json_obj(std::vector& l, JSONObj *obj) +inline void decode_json_obj(ceph::real_time& val, JSONObj *obj) { - l.clear(); - - JSONObjIter iter = obj->find_first(); - - for (; !iter.end(); ++iter) { - T val; - JSONObj *o = *iter; - decode_json_obj(val, o); - l.push_back(val); + uint64_t epoch; + uint64_t nsec; + int r = utime_t::parse_date(obj->get_data(), &epoch, &nsec); + if (r == 0) { + using namespace std::chrono; + val = real_time{seconds(epoch) + nanoseconds(nsec)}; + } else { + throw JSONDecoder::err("failed to decode real_time"); } } -template > -void decode_json_obj(std::map& m, JSONObj *obj) +inline void decode_json_obj(ceph::coarse_real_time& val, JSONObj *obj) { - m.clear(); - - JSONObjIter iter = obj->find_first(); - - for (; !iter.end(); ++iter) { - K key; - V val; - JSONObj *o = *iter; - JSONDecoder::decode_json("key", key, o); - JSONDecoder::decode_json("val", val, o); - m[key] = val; + uint64_t epoch; + uint64_t nsec; + int r = utime_t::parse_date(obj->get_data(), &epoch, &nsec); + if (r == 0) { + using namespace std::chrono; + val = coarse_real_time{seconds(epoch) + nanoseconds(nsec)}; + } else { + throw JSONDecoder::err("failed to decode coarse_real_time"); } } -template > -void decode_json_obj(boost::container::flat_map& m, JSONObj *obj) +inline void decode_json_obj(ceph_dir_layout& i, JSONObj *obj) { - m.clear(); - - JSONObjIter iter = obj->find_first(); - - for (; !iter.end(); ++iter) { - K key; - V val; - JSONObj *o = *iter; - JSONDecoder::decode_json("key", key, o); - JSONDecoder::decode_json("val", val, o); - m[key] = val; - } + unsigned tmp; + JSONDecoder::decode_json("dir_hash", tmp, obj, true); + i.dl_dir_hash = tmp; + JSONDecoder::decode_json("unused1", tmp, obj, true); + i.dl_unused1 = tmp; + JSONDecoder::decode_json("unused2", tmp, obj, true); + i.dl_unused2 = tmp; + JSONDecoder::decode_json("unused3", tmp, obj, true); + i.dl_unused3 = tmp; } -template -void decode_json_obj(std::multimap& m, JSONObj *obj) +template +void decode_json_obj(SeqT& seq, JSONObj *obj) { - m.clear(); - - JSONObjIter iter = obj->find_first(); + seq.clear(); - for (; !iter.end(); ++iter) { - K key; - V val; + for (auto iter = obj->find_first(); !iter.end(); ++iter) { + typename SeqT::value_type val; JSONObj *o = *iter; - JSONDecoder::decode_json("key", key, o); - JSONDecoder::decode_json("val", val, o); - m.insert(make_pair(key, val)); - } + decode_json_obj(val, o); + + if constexpr (requires { seq.emplace_back(val); }) + seq.emplace_back(val); + else + seq.emplace(val); + } } -template -void decode_json_obj(boost::container::flat_map& m, JSONObj *obj) +template +void decode_json_obj(KVSeqT& kvs, JSONObj *obj) { - m.clear(); - - JSONObjIter iter = obj->find_first(); + kvs.clear(); - for (; !iter.end(); ++iter) { - K key; - V val; + for (auto iter = obj->find_first(); !iter.end(); ++iter) { + typename KVSeqT::key_type key; + typename KVSeqT::mapped_type val; JSONObj *o = *iter; JSONDecoder::decode_json("key", key, o); JSONDecoder::decode_json("val", val, o); - m[key] = val; + + if constexpr(requires { kvs[key] = val; }) + kvs[key] = val; // i.e. insert_or_assign() + else + kvs.insert({key, val}); } } + template void decode_json_obj(C& container, void (*cb)(C&, JSONObj *obj), JSONObj *obj) { container.clear(); - JSONObjIter iter = obj->find_first(); - - for (; !iter.end(); ++iter) { + for (auto iter = obj->find_first(); !iter.end(); ++iter) { JSONObj *o = *iter; cb(container, o); } } template -bool JSONDecoder::decode_json(const char *name, T& val, JSONObj *obj, bool mandatory) +bool JSONDecoder::decode_json(std::string_view name, T& val, JSONObj *obj, bool mandatory) { JSONObjIter iter = obj->find_first(name); if (iter.end()) { @@ -379,7 +525,7 @@ bool JSONDecoder::decode_json(const char *name, T& val, JSONObj *obj, bool manda } template -bool JSONDecoder::decode_json(const char *name, C& container, void (*cb)(C&, JSONObj *), JSONObj *obj, bool mandatory) +bool JSONDecoder::decode_json(std::string_view name, C& container, void (*cb)(C&, JSONObj *), JSONObj *obj, bool mandatory) { container.clear(); @@ -404,7 +550,7 @@ bool JSONDecoder::decode_json(const char *name, C& container, void (*cb)(C&, JSO } template -void JSONDecoder::decode_json(const char *name, T& val, const T& default_val, JSONObj *obj) +void JSONDecoder::decode_json(std::string_view name, T& val, const T& default_val, JSONObj *obj) { JSONObjIter iter = obj->find_first(name); if (iter.end()) { @@ -423,7 +569,7 @@ void JSONDecoder::decode_json(const char *name, T& val, const T& default_val, JS } template -bool JSONDecoder::decode_json(const char *name, boost::optional& val, JSONObj *obj, bool mandatory) +bool JSONDecoder::decode_json(std::string_view name, boost::optional& val, JSONObj *obj, bool mandatory) { JSONObjIter iter = obj->find_first(name); if (iter.end()) { @@ -449,7 +595,7 @@ bool JSONDecoder::decode_json(const char *name, boost::optional& val, JSONObj } template -bool JSONDecoder::decode_json(const char *name, std::optional& val, JSONObj *obj, bool mandatory) +bool JSONDecoder::decode_json(std::string_view name, std::optional& val, JSONObj *obj, bool mandatory) { JSONObjIter iter = obj->find_first(name); if (iter.end()) { @@ -479,7 +625,7 @@ class JSONEncodeFilter public: class HandlerBase { public: - virtual ~HandlerBase() {} + virtual ~HandlerBase() = default; virtual std::type_index get_type() = 0; virtual void encode_json(const char *name, const void *pval, ceph::Formatter *) const = 0; @@ -488,8 +634,6 @@ public: template class Handler : public HandlerBase { public: - virtual ~Handler() {} - std::type_index get_type() override { return std::type_index(typeid(const T&)); } @@ -515,8 +659,19 @@ public: } }; +void encode_json(const char *name, ceph_json::detail::json_signed_integer auto val, Formatter *f) +{ + f->dump_int(name, val); +} + +void encode_json(const char *name, ceph_json::detail::json_unsigned_integer auto val, Formatter *f) +{ + f->dump_unsigned(name, val); +} + template -static void encode_json_impl(const char *name, const T& val, ceph::Formatter *f) +requires requires(const T& val, ceph::Formatter *f) { val.dump(f); } +void encode_json_impl(const char *name, const T& val, ceph::Formatter *f) { f->open_object_section(name); val.dump(f); @@ -524,7 +679,8 @@ static void encode_json_impl(const char *name, const T& val, ceph::Formatter *f) } template -static void encode_json(const char *name, const T& val, ceph::Formatter *f) +requires requires(const T& val, ceph::Formatter *f) { encode_json_impl("", val, f); } +void encode_json(const char *name, const T& val, ceph::Formatter *f) { JSONEncodeFilter *filter = static_cast(f->get_external_feature_handler("JSONEncodeFilter")); @@ -534,150 +690,82 @@ static void encode_json(const char *name, const T& val, ceph::Formatter *f) } } -class utime_t; - -void encode_json(const char *name, std::string_view val, ceph::Formatter *f); -void encode_json(const char *name, const std::string& val, ceph::Formatter *f); -void encode_json(const char *name, const char *val, ceph::Formatter *f); -void encode_json(const char *name, bool val, ceph::Formatter *f); -void encode_json(const char *name, int val, ceph::Formatter *f); -void encode_json(const char *name, unsigned val, ceph::Formatter *f); -void encode_json(const char *name, long val, ceph::Formatter *f); -void encode_json(const char *name, unsigned long val, ceph::Formatter *f); -void encode_json(const char *name, long long val, ceph::Formatter *f); -void encode_json(const char *name, const utime_t& val, ceph::Formatter *f); -void encode_json(const char *name, const ceph::buffer::list& bl, ceph::Formatter *f); -void encode_json(const char *name, long long unsigned val, ceph::Formatter *f); - -void encode_json(const char *name, const ceph::real_time& val, ceph::Formatter *f); -void encode_json(const char *name, const ceph::coarse_real_time& val, ceph::Formatter *f); - -template -static void encode_json(const char *name, const std::list& l, ceph::Formatter *f) +inline void encode_json(const char *name, std::string_view val, Formatter *f) { - f->open_array_section(name); - for (auto iter = l.cbegin(); iter != l.cend(); ++iter) { - encode_json("obj", *iter, f); - } - f->close_section(); + f->dump_string(name, val); } -template -static void encode_json(const char *name, const std::deque& l, ceph::Formatter *f) +inline void encode_json(const char *name, const std::string& val, Formatter *f) { - f->open_array_section(name); - for (auto iter = l.cbegin(); iter != l.cend(); ++iter) { - encode_json("obj", *iter, f); - } - f->close_section(); + f->dump_string(name, val); } -template > -static void encode_json(const char *name, const std::set& l, ceph::Formatter *f) +inline void encode_json(const char *name, const char *val, Formatter *f) { - f->open_array_section(name); - for (auto iter = l.cbegin(); iter != l.cend(); ++iter) { - encode_json("obj", *iter, f); - } - f->close_section(); + f->dump_string(name, val); } -template -static void encode_json(const char *name, - const boost::container::flat_set& l, - ceph::Formatter *f) +inline void encode_json(const char *name, bool val, Formatter *f) { - f->open_array_section(name); - for (auto iter = l.cbegin(); iter != l.cend(); ++iter) { - encode_json("obj", *iter, f); - } - f->close_section(); + f->dump_bool(name, val); } -template -static void encode_json(const char *name, const std::vector& l, ceph::Formatter *f) +inline void encode_json(const char *name, const utime_t& val, Formatter *f) { - f->open_array_section(name); - for (auto iter = l.cbegin(); iter != l.cend(); ++iter) { - encode_json("obj", *iter, f); - } - f->close_section(); + val.gmtime(f->dump_stream(name)); } -template -static void encode_json(const char *name, const std::array& l, - ceph::Formatter *f) +inline void encode_json(const char *name, const ceph::real_time& val, Formatter *f) { - f->open_array_section(name); - for (auto iter = l.cbegin(); iter != l.cend(); ++iter) { - encode_json("obj", *iter, f); - } - f->close_section(); + encode_json(name, utime_t{val}, f); } -template> -static void encode_json(const char *name, const std::map& m, ceph::Formatter *f) +inline void encode_json(const char *name, const ceph::coarse_real_time& val, Formatter *f) { - f->open_array_section(name); - for (auto i = m.cbegin(); i != m.cend(); ++i) { - f->open_object_section("entry"); - encode_json("key", i->first, f); - encode_json("val", i->second, f); - f->close_section(); - } - f->close_section(); + encode_json(name, utime_t{val}, f); } -template > -static void encode_json(const char *name, const boost::container::flat_map& m, ceph::Formatter *f) +inline void encode_json(const char *name, const bufferlist& bl, Formatter *f) { - f->open_array_section(name); - for (auto i = m.cbegin(); i != m.cend(); ++i) { - f->open_object_section("entry"); - encode_json("key", i->first, f); - encode_json("val", i->second, f); - f->close_section(); - } - f->close_section(); + /* need to copy data from bl, as it is const bufferlist */ + bufferlist src = bl; + + bufferlist b64; + src.encode_base64(b64); + + std::string_view sv(b64.c_str(), b64.length()); + + encode_json(name, sv, f); } -template -static void encode_json(const char *name, const std::multimap& m, ceph::Formatter *f) +template +void encode_json(const char *name, const std::optional& o, ceph::Formatter *f) { - f->open_array_section(name); - for (auto i = m.begin(); i != m.end(); ++i) { - f->open_object_section("entry"); - encode_json("key", i->first, f); - encode_json("val", i->second, f); - f->close_section(); + if (!o) { + return; } - f->close_section(); + encode_json(name, *o, f); } -template -static void encode_json(const char *name, const boost::container::flat_map& m, ceph::Formatter *f) +inline void encode_json(const char *name, const JSONObj::data_val& v, Formatter *f) { - f->open_array_section(name); - for (auto i = m.begin(); i != m.end(); ++i) { - f->open_object_section("entry"); - encode_json("key", i->first, f); - encode_json("val", i->second, f); - f->close_section(); + if (v.quoted) { + encode_json(name, v.str, f); + } else { + f->dump_format_unquoted(name, "%s", v.str.c_str()); } - f->close_section(); } +inline void encode_json(const char *name, const JSONFormattable& v, Formatter *f); + template void encode_json_map(const char *name, const std::map& m, ceph::Formatter *f) { f->open_array_section(name); - for (auto iter = m.cbegin(); iter != m.cend(); ++iter) { - encode_json("obj", iter->second, f); - } + std::ranges::for_each(m, [&f](const auto& kv) { encode_json("obj", kv.second, f); }); f->close_section(); } - template void encode_json_map(const char *name, const char *index_name, const char *object_name, const char *value_name, @@ -716,27 +804,17 @@ void encode_json_map(const char *name, const char *index_name, const char *object_name, const char *value_name, const std::map& m, ceph::Formatter *f) { - encode_json_map(name, index_name, object_name, value_name, NULL, NULL, m, f); + encode_json_map(name, index_name, object_name, value_name, nullptr, nullptr, m, f); } template void encode_json_map(const char *name, const char *index_name, const char *value_name, const std::map& m, ceph::Formatter *f) { - encode_json_map(name, index_name, NULL, value_name, NULL, NULL, m, f); + encode_json_map(name, index_name, nullptr, value_name, nullptr, nullptr, m, f); } -template -static void encode_json(const char *name, const std::optional& o, ceph::Formatter *f) -{ - if (!o) { - return; - } - encode_json(name, *o, f); -} - - -template +template void encode_json_map(const char *name, const boost::container::flat_map& m, ceph::Formatter *f) { f->open_array_section(name); @@ -746,7 +824,6 @@ void encode_json_map(const char *name, const boost::container::flat_map& m f->close_section(); } - template void encode_json_map(const char *name, const char *index_name, const char *object_name, const char *value_name, @@ -785,21 +862,42 @@ void encode_json_map(const char *name, const char *index_name, const char *object_name, const char *value_name, const boost::container::flat_map& m, ceph::Formatter *f) { - encode_json_map(name, index_name, object_name, value_name, NULL, NULL, m, f); + encode_json_map(name, index_name, object_name, value_name, nullptr, nullptr, m, f); } template void encode_json_map(const char *name, const char *index_name, const char *value_name, const boost::container::flat_map& m, ceph::Formatter *f) { - encode_json_map(name, index_name, NULL, value_name, NULL, NULL, m, f); + encode_json_map(name, index_name, nullptr, value_name, nullptr, nullptr, m, f); } +void encode_json(const char *name, const ceph_json::detail::json_val_seq auto& val, Formatter *f) +{ + f->open_array_section(name); + std::ranges::for_each(val, [&f](const auto &obj) { + ::encode_json("obj", obj, f); + }); + f->close_section(); +} + +void encode_json(const char *name, const ceph_json::detail::json_mapped_kv_seq auto& val, Formatter *f) +{ + f->open_array_section(name); + std::ranges::for_each(val, [&f](const auto& kv) { + f->open_object_section("entry"); + ::encode_json("key", kv.first, f); + ::encode_json("val", kv.second, f); + f->close_section(); + }); + f->close_section(); +} class JSONFormattable : public ceph::JSONFormatter { + JSONObj::data_val value; std::vector arr; - std::map obj; + std::map> obj; std::vector enc_stack; JSONFormattable *cur_enc; @@ -810,7 +908,8 @@ protected: bool handle_close_section() override; public: - JSONFormattable(bool p = false) : JSONFormatter(p) { + JSONFormattable(bool p = false) + : JSONFormatter(p) { cur_enc = this; enc_stack.push_back(cur_enc); } @@ -896,6 +995,61 @@ public: break; } } + + const std::map> object() const noexcept { return obj; } + + const std::vector& array() const noexcept { return arr; } + + JSONFormattable& operator[](const std::string& name); + const JSONFormattable& operator[](const std::string& name) const; + + JSONFormattable& operator[](size_t index); + const JSONFormattable& operator[](size_t index) const; + + const std::string& val() const noexcept { return value.str; } + int val_int() const { return atoi(value.str.c_str()); } + long val_long() const { return atol(value.str.c_str()); } + long long val_long_long() const { return atoll(value.str.c_str()); } + bool val_bool() const; + + operator std::string() const noexcept { return value.str; } + explicit operator int() const { return val_int(); } + explicit operator long() const { return val_long(); } + explicit operator long long() const { return val_long_long(); } + explicit operator bool() const { return val_bool(); } + + std::string def(const std::string& def_val) const { return FMT_NONE == type ? def_val : val(); } + int def(int def_val) const { return FMT_NONE == type ? def_val : val_int(); } + bool def(bool def_val) const { return FMT_NONE == type ? def_val : val_bool(); } + + std::string operator ()(const char *def_val) const { return def(std::string(def_val)); } + int operator()(int def_val) const { return def(def_val); } + bool operator()(bool def_val) const { return def(def_val); } + + bool exists(const std::string& name) const noexcept { return obj.contains(name); } + bool exists(size_t index) const noexcept { return (index < arr.size()); } + + bool find(const std::string& name, std::string *val) const noexcept { + if (auto i = obj.find(name); end(obj) != i) + return (*val = i->second.val()), true; + + return false; + } + + std::string get(const std::string& name, const std::string& def_val) const { return (*this)[name].def(def_val); } + int get_int(const std::string& name, int def_val) const { return (*this)[name].def(def_val); } + bool get_bool(const std::string& name, bool def_val) const { return (*this)[name].def(def_val); } + + int set(const std::string& name, const std::string& val); + int erase(const std::string& name); + + void derive_from(const JSONFormattable& jf); + + void encode_json(const char *name, ceph::Formatter *f) const; + + bool is_array() const { return type == FMT_ARRAY; } + +public: static void generate_test_instances(std::list& o) { o.push_back(new JSONFormattable); o.push_back(new JSONFormattable); @@ -924,98 +1078,54 @@ public: o.back()->obj["foo"].value.quoted = true; } - const std::string& val() const { - return value.str; - } - - int val_int() const; - long val_long() const; - long long val_long_long() const; - bool val_bool() const; - - const std::map object() const { - return obj; - } - - const std::vector& array() const { - return arr; - } - - const JSONFormattable& operator[](const std::string& name) const; - const JSONFormattable& operator[](size_t index) const; - - JSONFormattable& operator[](const std::string& name); - JSONFormattable& operator[](size_t index); - - operator std::string() const { - return value.str; - } - - explicit operator int() const { - return val_int(); - } - - explicit operator long() const { - return val_long(); - } - - explicit operator long long() const { - return val_long_long(); - } - - explicit operator bool() const { - return val_bool(); - } - - template - T operator[](const std::string& name) const { - return this->operator[](name)(T()); - } - - template - T operator[](const std::string& name) { - return this->operator[](name)(T()); - } - - std::string operator ()(const char *def_val) const { - return def(std::string(def_val)); - } - - int operator()(int def_val) const { - return def(def_val); - } - - bool operator()(bool def_val) const { - return def(def_val); - } - - bool exists(const std::string& name) const; - bool exists(size_t index) const; - - std::string def(const std::string& def_val) const; - int def(int def_val) const; - bool def(bool def_val) const; +}; +WRITE_CLASS_ENCODER(JSONFormattable) - bool find(const std::string& name, std::string *val) const; +static inline JSONFormattable default_formattable; - std::string get(const std::string& name, const std::string& def_val) const; +inline JSONFormattable& JSONFormattable::operator[](const std::string& name) { + if (const auto i = obj.find(name); end(obj) != i) + return i->second; + + return default_formattable; +} - int get_int(const std::string& name, int def_val) const; - bool get_bool(const std::string& name, bool def_val) const; +inline const JSONFormattable& JSONFormattable::operator[](const std::string& name) const { + return const_cast(this)->operator[](name); +} - int set(const std::string& name, const std::string& val); - int erase(const std::string& name); +inline JSONFormattable& JSONFormattable::operator[](size_t index) { + return index >= arr.size() ? default_formattable : arr[index]; +} - void derive_from(const JSONFormattable& jf); +inline const JSONFormattable& JSONFormattable::operator[](size_t index) const { + return const_cast(this)->operator[](index); +} - void encode_json(const char *name, ceph::Formatter *f) const; +inline void encode_json(const char *name, const JSONFormattable& v, Formatter *f) +{ + v.encode_json(name, f); +} - bool is_array() const { - return (type == FMT_ARRAY); +inline void JSONFormattable::encode_json(const char *name, Formatter *f) const +{ + switch (type) { + case JSONFormattable::FMT_VALUE: + ::encode_json(name, value, f); + break; + case JSONFormattable::FMT_ARRAY: + ::encode_json(name, arr, f); + break; + case JSONFormattable::FMT_OBJ: + f->open_object_section(name); + for (auto iter : obj) { + ::encode_json(iter.first.c_str(), iter.second, f); + } + f->close_section(); + break; + case JSONFormattable::FMT_NONE: + break; } -}; -WRITE_CLASS_ENCODER(JSONFormattable) - -void encode_json(const char *name, const JSONFormattable& v, ceph::Formatter *f); +} #endif diff --git a/src/mgr/Mgr.cc b/src/mgr/Mgr.cc index 7e0b4c26f1c..ccd35375a9b 100644 --- a/src/mgr/Mgr.cc +++ b/src/mgr/Mgr.cc @@ -15,6 +15,7 @@ #include "osdc/Objecter.h" #include "common/errno.h" +#include "common/ceph_json.h" #include "mon/MonClient.h" #include "include/stringify.h" #include "global/global_context.h" @@ -79,7 +80,6 @@ Mgr::~Mgr() } void MetadataUpdate::finish(int r) -try { daemon_state.clear_updating(key); if (r == 0) { @@ -87,8 +87,8 @@ try key.type == "mgr" || key.type == "mon") { std::error_code ec; - - boost::json::value json_result = boost::json::parse(outbl.to_str(), ec); + auto json_result = boost::json::parse(outbl.to_str(), ec, boost::json::storage_ptr(), + { .allow_invalid_utf8 = true }); if (ec) { dout(1) << "mon returned invalid JSON for " << key << dendl; @@ -162,10 +162,6 @@ try << ": " << cpp_strerror(r) << dendl; } } -catch(const std::exception& e) -{ - dout(1) << "!!JFW: mon caught: " << e.what() << dendl; -} void Mgr::background_init(Context *completion) { @@ -197,7 +193,7 @@ std::map Mgr::load_store() std::map loaded; for (auto &key_str : cmd.json_result.get_array()) { - std::string const key = key_str.get_string().c_str(); + boost::json::string_view key = key_str.get_string(); dout(20) << "saw key '" << key << "'" << dendl; @@ -401,7 +397,6 @@ void Mgr::init() } void Mgr::load_all_metadata() -try { ceph_assert(ceph_mutex_is_locked_by_me(lock)); @@ -430,15 +425,15 @@ try } DaemonStatePtr dm = std::make_shared(daemon_state.types); - dm->key = DaemonKey{"mds", - daemon_meta.at("name").get_string().c_str()}; - dm->hostname = daemon_meta.at("hostname").get_string().c_str(); + dm->key = DaemonKey{"mds", boost::json::value_to(daemon_meta.at("name")) }; + dm->hostname = boost::json::value_to(daemon_meta.at("hostname")); + daemon_meta.erase("name"); daemon_meta.erase("hostname"); for (const auto &[key, val] : daemon_meta) { - dm->metadata.emplace(key, val.get_string()); + dm->metadata.emplace(key, boost::json::value_to(val)); } daemon_state.insert(dm); @@ -452,9 +447,8 @@ try } DaemonStatePtr dm = std::make_shared(daemon_state.types); - dm->key = DaemonKey{"mon", - daemon_meta.at("name").get_string().c_str()}; - dm->hostname = daemon_meta.at("hostname").get_string().c_str(); + dm->key = DaemonKey{"mon", boost::json::value_to(daemon_meta.at("name")) }; + dm->hostname = daemon_meta.at("hostname").get_string(); daemon_meta.erase("name"); daemon_meta.erase("hostname"); @@ -493,10 +487,6 @@ try daemon_state.insert(dm); } } -catch(const std::exception& e) -{ - dout(1) << "JFW: load_all_metadata(): caught " << e.what() << dendl; -} void Mgr::handle_osd_map() { diff --git a/src/mgr/MgrContext.h b/src/mgr/MgrContext.h index 5b4f8727fde..eb17a52a534 100644 --- a/src/mgr/MgrContext.h +++ b/src/mgr/MgrContext.h @@ -67,14 +67,6 @@ public: if(ec) r = -EINVAL; - -/*JFW: - bool read_ok = json_spirit::read( - outbl.to_str(), json_result); - if (!read_ok) { - r = -EINVAL; - } -*/ } } }; diff --git a/src/rgw/rgw_acl_swift.cc b/src/rgw/rgw_acl_swift.cc index 67e0daf5b72..e7ed5a39348 100644 --- a/src/rgw/rgw_acl_swift.cc +++ b/src/rgw/rgw_acl_swift.cc @@ -286,7 +286,7 @@ int create_account_policy(const DoutPrefixProvider* dpp, auto& acl = policy.get_acl(); JSONParser parser; - if (!parser.parse(acl_str.c_str(), acl_str.length())) { + if (!parser.parse(acl_str)) { ldpp_dout(dpp, 0) << "ERROR: JSONParser::parse returned error=" << dendl; return -EINVAL; } diff --git a/src/rgw/rgw_auth_keystone.cc b/src/rgw/rgw_auth_keystone.cc index 7f3bd66a1b9..33bf75bb347 100644 --- a/src/rgw/rgw_auth_keystone.cc +++ b/src/rgw/rgw_auth_keystone.cc @@ -546,7 +546,7 @@ auto EC2Engine::get_secret_from_keystone(const DoutPrefixProvider* dpp, /* now parse response */ JSONParser parser; - if (! parser.parse(token_body_bl.c_str(), token_body_bl.length())) { + if (! parser.parse(token_body_bl)) { ldpp_dout(dpp, 0) << "Keystone credential parse error: malformed json" << dendl; return make_pair(boost::none, -EINVAL); } diff --git a/src/rgw/rgw_common.cc b/src/rgw/rgw_common.cc index 5ecb2a1e17d..7336c6ae546 100644 --- a/src/rgw/rgw_common.cc +++ b/src/rgw/rgw_common.cc @@ -7,7 +7,6 @@ #include #include -#include "json_spirit/json_spirit.h" #include "common/ceph_json.h" #include "common/Formatter.h" #include "common/versioned_variant.h" diff --git a/src/rgw/rgw_keystone.cc b/src/rgw/rgw_keystone.cc index 2767cea06de..aab3b47ba1b 100644 --- a/src/rgw/rgw_keystone.cc +++ b/src/rgw/rgw_keystone.cc @@ -285,7 +285,7 @@ int TokenEnvelope::parse(const DoutPrefixProvider *dpp, ceph::bufferlist& bl) { JSONParser parser; - if (! parser.parse(bl.c_str(), bl.length())) { + if (! parser.parse(bl)) { ldpp_dout(dpp, 0) << "Keystone token parse error: malformed json" << dendl; return -EINVAL; } diff --git a/src/rgw/rgw_metadata.cc b/src/rgw/rgw_metadata.cc index 92b03e41bcd..65ada37dcc0 100644 --- a/src/rgw/rgw_metadata.cc +++ b/src/rgw/rgw_metadata.cc @@ -309,7 +309,7 @@ int RGWMetadataManager::put(string& metadata_key, bufferlist& bl, } JSONParser parser; - if (!parser.parse(bl.c_str(), bl.length())) { + if (!parser.parse(bl)) { return -EINVAL; } diff --git a/src/rgw/rgw_op.cc b/src/rgw/rgw_op.cc index a96eb3ef602..50ed075d7a8 100644 --- a/src/rgw/rgw_op.cc +++ b/src/rgw/rgw_op.cc @@ -159,7 +159,7 @@ int rgw_forward_request_to_master(const DoutPrefixProvider* dpp, if (ret < 0) { return ret; } - if (jp && !jp->parse(outdata.c_str(), outdata.length())) { + if (jp && !jp->parse(outdata)) { ldpp_dout(dpp, 0) << "failed parsing response from master zonegroup" << dendl; return -EINVAL; } diff --git a/src/rgw/rgw_op.h b/src/rgw/rgw_op.h index cbe441c140b..a0314cf1c3e 100644 --- a/src/rgw/rgw_op.h +++ b/src/rgw/rgw_op.h @@ -158,7 +158,7 @@ int rgw_rest_get_json_input(CephContext *cct, req_state *s, T& out, JSONParser parser; - if (!parser.parse(data.c_str(), data.length())) { + if (!parser.parse(data)) { return -EINVAL; } diff --git a/src/rgw/rgw_opa.cc b/src/rgw/rgw_opa.cc index 0bda4d62a51..93b8693dbdf 100644 --- a/src/rgw/rgw_opa.cc +++ b/src/rgw/rgw_opa.cc @@ -79,7 +79,7 @@ int rgw_opa_authorize(RGWOp *& op, /* check OPA response */ JSONParser parser; - if (!parser.parse(bl.c_str(), bl.length())) { + if (!parser.parse(bl)) { ldpp_dout(op, 2) << "OPA parse error: malformed json" << dendl; return -EINVAL; } diff --git a/src/rgw/rgw_period_puller.cc b/src/rgw/rgw_period_puller.cc index ea2f28e567b..6a8cca0aedc 100644 --- a/src/rgw/rgw_period_puller.cc +++ b/src/rgw/rgw_period_puller.cc @@ -46,7 +46,7 @@ int pull_period(const DoutPrefixProvider *dpp, RGWRESTConn* conn, const std::str } JSONParser parser; - r = parser.parse(data.c_str(), data.length()); + r = parser.parse(data); if (r < 0) { ldpp_dout(dpp, -1) << "request failed: " << cpp_strerror(-r) << dendl; return r; diff --git a/src/rgw/rgw_policy_s3.cc b/src/rgw/rgw_policy_s3.cc index 7b8ad0452cb..f9d324fc36f 100644 --- a/src/rgw/rgw_policy_s3.cc +++ b/src/rgw/rgw_policy_s3.cc @@ -248,7 +248,7 @@ int RGWPolicy::from_json(bufferlist& bl, string& err_msg) { JSONParser parser; - if (!parser.parse(bl.c_str(), bl.length())) { + if (!parser.parse(bl)) { err_msg = "Malformed JSON"; dout(0) << "malformed json" << dendl; return -EINVAL; diff --git a/src/rgw/rgw_rest.h b/src/rgw/rgw_rest.h index a302257e428..a5e7bdbf56a 100644 --- a/src/rgw/rgw_rest.h +++ b/src/rgw/rgw_rest.h @@ -62,7 +62,7 @@ std::tuple rgw_rest_get_json_input_keep_data(CephContext *cct, JSONParser parser; - if (!parser.parse(data.c_str(), data.length())) { + if (!parser.parse(data)) { return std::make_tuple(-EINVAL, std::move(data)); } diff --git a/src/rgw/rgw_rest_conn.h b/src/rgw/rgw_rest_conn.h index 7abf86a3d3f..13bc6077319 100644 --- a/src/rgw/rgw_rest_conn.h +++ b/src/rgw/rgw_rest_conn.h @@ -17,7 +17,7 @@ template inline int parse_decode_json(T& t, bufferlist& bl) { JSONParser p; - if (!p.parse(bl.c_str(), bl.length())) { + if (!p.parse(bl)) { return -EINVAL; } diff --git a/src/rgw/rgw_rest_role.cc b/src/rgw/rgw_rest_role.cc index a3733c175cc..7e924c640ff 100644 --- a/src/rgw/rgw_rest_role.cc +++ b/src/rgw/rgw_rest_role.cc @@ -449,7 +449,7 @@ int RGWModifyRoleTrustPolicy::init_processing(optional_yield y) } JSONParser p; - if (!p.parse(trust_policy.c_str(), trust_policy.length())) { + if (!p.parse(trust_policy)) { ldpp_dout(this, 20) << "ERROR: failed to parse assume role policy doc" << dendl; return -ERR_MALFORMED_DOC; } diff --git a/src/rgw/rgw_rest_sts.cc b/src/rgw/rgw_rest_sts.cc index c0c6b5e89ac..7b36511f0e4 100644 --- a/src/rgw/rgw_rest_sts.cc +++ b/src/rgw/rgw_rest_sts.cc @@ -327,7 +327,7 @@ WebTokenEngine::get_cert_url(const string& iss, const DoutPrefixProvider *dpp, o ldpp_dout(dpp, 20) << "JSON Response is: " << openidc_resp.c_str() << dendl; JSONParser parser; - if (parser.parse(openidc_resp.c_str(), openidc_resp.length())) { + if (parser.parse(openidc_resp)) { JSONObj::data_val val; if (parser.get_data("jwks_uri", &val)) { cert_url = val.str.c_str(); @@ -364,7 +364,7 @@ WebTokenEngine::validate_signature(const DoutPrefixProvider* dpp, const jwt::dec ldpp_dout(dpp, 20) << "JSON Response is: " << cert_resp.c_str() << dendl; JSONParser parser; - if (parser.parse(cert_resp.c_str(), cert_resp.length())) { + if (parser.parse(cert_resp)) { JSONObj::data_val val; if (parser.get_data("keys", &val)) { if (val.str[0] == '[') { diff --git a/src/rgw/rgw_token.h b/src/rgw/rgw_token.h index b2476596bec..3bf9adaf00b 100644 --- a/src/rgw/rgw_token.h +++ b/src/rgw/rgw_token.h @@ -82,13 +82,13 @@ namespace rgw { explicit RGWToken(const string& json) { JSONParser p; - p.parse(json.c_str(), json.length()); + p.parse(json); JSONDecoder::decode_json(RGWToken::type_name, *this, &p); } RGWToken& operator=(const std::string& json) { JSONParser p; - p.parse(json.c_str(), json.length()); + p.parse(json); JSONDecoder::decode_json(RGWToken::type_name, *this, &p); return *this; } diff --git a/src/test/common/test_json_formattable.cc b/src/test/common/test_json_formattable.cc index 013736f0ec8..2da3e67819f 100644 --- a/src/test/common/test_json_formattable.cc +++ b/src/test/common/test_json_formattable.cc @@ -37,7 +37,7 @@ static void get_jf(const string& s, JSONFormattable *f) } } -TEST(formatable, str) { +TEST(formattable, str) { JSONFormattable f; get_jf("{ \"foo\": \"bar\" }", &f); ASSERT_EQ((string)f["foo"], "bar"); @@ -45,7 +45,7 @@ TEST(formatable, str) { ASSERT_EQ((string)f["fooz"]("lala"), "lala"); } -TEST(formatable, str2) { +TEST(formattable, str2) { JSONFormattable f; get_jf("{ \"foo\": \"bar\" }", &f); ASSERT_EQ((string)f["foo"], "bar"); @@ -61,13 +61,13 @@ TEST(formatable, str2) { } -TEST(formatable, str3) { +TEST(formattable, str3) { JSONFormattable f; get_jf("{ \"foo\": \"1234bar56\" }", &f); ASSERT_EQ((string)f["foo"], "1234bar56"); } -TEST(formatable, int) { +TEST(formattable, int) { JSONFormattable f; get_jf("{ \"foo\": 1 }", &f); ASSERT_EQ((int)f["foo"], 1); @@ -82,7 +82,7 @@ TEST(formatable, int) { ASSERT_EQ((int)f2["fooz"](111), 123); } -TEST(formatable, bool) { +TEST(formattable, bool) { JSONFormattable f; get_jf("{ \"foo\": \"true\" }", &f); ASSERT_EQ((bool)f["foo"], true); @@ -94,7 +94,7 @@ TEST(formatable, bool) { ASSERT_EQ((bool)f["foo"], false); } -TEST(formatable, nested) { +TEST(formattable, nested) { JSONFormattable f; get_jf("{ \"obj\": { \"foo\": 1, \"inobj\": { \"foo\": 2 } } }", &f); ASSERT_EQ((int)f["foo"], 0); @@ -102,7 +102,7 @@ TEST(formatable, nested) { ASSERT_EQ((int)f["obj"]["inobj"]["foo"], 2); } -TEST(formatable, array) { +TEST(formattable, array) { JSONFormattable f; get_jf("{ \"arr\": [ { \"foo\": 1, \"inobj\": { \"foo\": 2 } }," "{ \"foo\": 2 } ] }", &f); @@ -123,7 +123,7 @@ TEST(formatable, array) { } } -TEST(formatable, bin_encode) { +TEST(formattable, bin_encode) { JSONFormattable f, f2; get_jf("{ \"arr\": [ { \"foo\": 1, \"bar\": \"aaa\", \"inobj\": { \"foo\": 2 } }," "{ \"foo\": 2, \"inobj\": { \"foo\": 3 } } ] }", &f); @@ -155,7 +155,7 @@ TEST(formatable, bin_encode) { } -TEST(formatable, json_encode) { +TEST(formattable, json_encode) { JSONFormattable f, f2; get_jf("{ \"arr\": [ { \"foo\": 1, \"bar\": \"aaa\", \"inobj\": { \"foo\": 2 } }," "{ \"foo\": 2, \"inobj\": { \"foo\": 3 } } ] }", &f); @@ -180,7 +180,7 @@ TEST(formatable, json_encode) { } -TEST(formatable, set) { +TEST(formattable, set) { JSONFormattable f, f2; f.set("", "{ \"abc\": \"xyz\"}"); @@ -199,13 +199,13 @@ TEST(formatable, set) { ASSERT_EQ((int)f["obj"]["c"], 30); } -TEST(formatable, set2) { +TEST(formattable, set2) { JSONFormattable f; f.set("foo", "1234bar56"); ASSERT_EQ((string)f["foo"], "1234bar56"); } -TEST(formatable, erase) { +TEST(formattable, erase) { JSONFormattable f, f2; f.set("", "{ \"abc\": \"xyz\"}"); @@ -239,7 +239,7 @@ static void dumpf(const JSONFormattable& f) { dumpt(f, "f"); } -TEST(formatable, set_array) { +TEST(formattable, set_array) { JSONFormattable f, f2; f.set("asd[0]", "\"xyz\""); @@ -271,7 +271,7 @@ TEST(formatable, set_array) { ASSERT_EQ((string)f2[0]["field"], "xyz"); } -TEST(formatable, erase_array) { +TEST(formattable, erase_array) { JSONFormattable f; f.set("asd[0]", "\"xyz\""); @@ -314,7 +314,7 @@ void formatter_convert(JSONFormatter& formatter, JSONFormattable *dest) get_jf(ss.str(), dest); } -TEST(formatable, encode_simple) { +TEST(formattable, encode_simple) { JSONFormattable f; encode_json("foo", "bar", &f); @@ -421,7 +421,7 @@ struct struct2 { }; -TEST(formatable, encode_struct) { +TEST(formattable, encode_struct) { JSONFormattable f; struct2 s2; diff --git a/src/test/common/test_json_formatter.cc b/src/test/common/test_json_formatter.cc index d0ddd262c0a..144a52ef524 100644 --- a/src/test/common/test_json_formatter.cc +++ b/src/test/common/test_json_formatter.cc @@ -3,6 +3,7 @@ /* * Ceph - scalable distributed file system * + * Copyright (C) 2025 IBM * Copyright (C) 2018 Red Hat Inc. * * This library is free software; you can redistribute it and/or @@ -23,14 +24,12 @@ using namespace std; +const string outstring = +"{\"pg_ready\":true, \"pg_stats\":[ { \"pgid\":\"1.0\", \"version\":\"16'56\",\"reported_seq\":\"62\",\"reported_epoch\":\"20\",\"state\":\"active+clean+inconsistent\",\"last_fresh\":\"2018-12-18 15:21:22.173804\",\"last_change\":\"2018-12-18 15:21:22.173804\",\"last_active\":\"2018-12-18 15:21:22.173804\",\"last_peered\":\"2018-12-18 15:21:22.173804\",\"last_clean\":\"2018-12-18 15:21:22.173804\",\"last_became_active\":\"2018-12-18 15:21:21.347685\",\"last_became_peered\":\"2018-12-18 15:21:21.347685\",\"last_unstale\":\"2018-12-18 15:21:22.173804\",\"last_undegraded\":\"2018-12-18 15:21:22.173804\",\"last_fullsized\":\"2018-12-18 15:21:22.173804\",\"mapping_epoch\":19,\"log_start\":\"0'0\",\"ondisk_log_start\":\"0'0\",\"created\":7,\"last_epoch_clean\":20,\"parent\":\"0.0\",\"parent_split_bits\":0,\"last_scrub\":\"16'56\",\"last_scrub_stamp\":\"2018-12-18 15:21:22.173684\",\"last_deep_scrub\":\"0'0\",\"last_deep_scrub_stamp\":\"2018-12-18 15:21:06.514438\",\"last_clean_scrub_stamp\":\"2018-12-18 15:21:06.514438\",\"log_size\":56,\"ondisk_log_size\":56,\"stats_invalid\":false,\"dirty_stats_invalid\":false,\"omap_stats_invalid\":false,\"hitset_stats_invalid\":false,\"hitset_bytes_stats_invalid\":false,\"pin_stats_invalid\":false,\"manifest_stats_invalid\":false,\"snaptrimq_len\":0,\"stat_sum\":{\"num_bytes\":24448,\"num_objects\":36,\"num_object_clones\":20,\"num_object_copies\":36,\"num_objects_missing_on_primary\":0,\"num_objects_missing\":0,\"num_objects_degraded\":0,\"num_objects_misplaced\":0,\"num_objects_unfound\":0,\"num_objects_dirty\":36,\"num_whiteouts\":3,\"num_read\":0,\"num_read_kb\":0,\"num_write\":36,\"num_write_kb\":50,\"num_scrub_errors\":20,\"num_shallow_scrub_errors\":20,\"num_deep_scrub_errors\":0,\"num_objects_recovered\":0,\"num_bytes_recovered\":0,\"num_keys_recovered\":0,\"num_objects_omap\":0,\"num_objects_hit_set_archive\":0,\"num_bytes_hit_set_archive\":0,\"num_flush\":0,\"num_flush_kb\":0,\"num_evict\":0,\"num_evict_kb\":0,\"num_promote\":0,\"num_flush_mode_high\":0,\"num_flush_mode_low\":0,\"num_evict_mode_some\":0,\"num_evict_mode_full\":0,\"num_objects_pinned\":0,\"num_legacy_snapsets\":0,\"num_large_omap_objects\":0,\"num_objects_manifest\":0},\"up\":[0],\"acting\":[0],\"blocked_by\":[],\"up_primary\":0,\"acting_primary\":0,\"purged_snaps\":[] }]}"; TEST(formatter, bug_37706) { vector pgs; - string outstring = -"{\"pg_ready\":true, \"pg_stats\":[ { \"pgid\":\"1.0\", \"version\":\"16'56\",\"reported_seq\":\"62\",\"reported_epoch\":\"20\",\"state\":\"active+clean+inconsistent\",\"last_fresh\":\"2018-12-18 15:21:22.173804\",\"last_change\":\"2018-12-18 15:21:22.173804\",\"last_active\":\"2018-12-18 15:21:22.173804\",\"last_peered\":\"2018-12-18 15:21:22.173804\",\"last_clean\":\"2018-12-18 15:21:22.173804\",\"last_became_active\":\"2018-12-18 15:21:21.347685\",\"last_became_peered\":\"2018-12-18 15:21:21.347685\",\"last_unstale\":\"2018-12-18 15:21:22.173804\",\"last_undegraded\":\"2018-12-18 15:21:22.173804\",\"last_fullsized\":\"2018-12-18 15:21:22.173804\",\"mapping_epoch\":19,\"log_start\":\"0'0\",\"ondisk_log_start\":\"0'0\",\"created\":7,\"last_epoch_clean\":20,\"parent\":\"0.0\",\"parent_split_bits\":0,\"last_scrub\":\"16'56\",\"last_scrub_stamp\":\"2018-12-18 15:21:22.173684\",\"last_deep_scrub\":\"0'0\",\"last_deep_scrub_stamp\":\"2018-12-18 15:21:06.514438\",\"last_clean_scrub_stamp\":\"2018-12-18 15:21:06.514438\",\"log_size\":56,\"ondisk_log_size\":56,\"stats_invalid\":false,\"dirty_stats_invalid\":false,\"omap_stats_invalid\":false,\"hitset_stats_invalid\":false,\"hitset_bytes_stats_invalid\":false,\"pin_stats_invalid\":false,\"manifest_stats_invalid\":false,\"snaptrimq_len\":0,\"stat_sum\":{\"num_bytes\":24448,\"num_objects\":36,\"num_object_clones\":20,\"num_object_copies\":36,\"num_objects_missing_on_primary\":0,\"num_objects_missing\":0,\"num_objects_degraded\":0,\"num_objects_misplaced\":0,\"num_objects_unfound\":0,\"num_objects_dirty\":36,\"num_whiteouts\":3,\"num_read\":0,\"num_read_kb\":0,\"num_write\":36,\"num_write_kb\":50,\"num_scrub_errors\":20,\"num_shallow_scrub_errors\":20,\"num_deep_scrub_errors\":0,\"num_objects_recovered\":0,\"num_bytes_recovered\":0,\"num_keys_recovered\":0,\"num_objects_omap\":0,\"num_objects_hit_set_archive\":0,\"num_bytes_hit_set_archive\":0,\"num_flush\":0,\"num_flush_kb\":0,\"num_evict\":0,\"num_evict_kb\":0,\"num_promote\":0,\"num_flush_mode_high\":0,\"num_flush_mode_low\":0,\"num_evict_mode_some\":0,\"num_evict_mode_full\":0,\"num_objects_pinned\":0,\"num_legacy_snapsets\":0,\"num_large_omap_objects\":0,\"num_objects_manifest\":0},\"up\":[0],\"acting\":[0],\"blocked_by\":[],\"up_primary\":0,\"acting_primary\":0,\"purged_snaps\":[] }]}"; - - JSONParser parser; ASSERT_TRUE(parser.parse(outstring.c_str(), outstring.size())); @@ -126,3 +125,62 @@ TEST(formatter, dump_large_item) { EXPECT_TRUE(parser.parse(bl.c_str(), bl.length())); EXPECT_EQ(parser.find_obj("Location")->get_data(), full_url); } + +TEST(formatter, parse_types) { + // Check that JSONParser::parse() works with expected data types + // (otherwise at least indirectly tested elsewhere): + + const auto json_input = R"({"expiration": "2025-01-17T10:26:46Z", "conditions": [{"bucket": "user-hqfadib9zxyj2pygevewzf45t-1"}, ["starts-with", "$key", "foo"], {"acl": "private"}, ["starts-with", "$Content-Type", "text/plain"], ["content-length-range", 0, 1024]]})"; + + { + JSONParser parser; + + ASSERT_TRUE(parser.parse(outstring)); + + JSONObj *pgstat_obj = parser.find_obj("pg_stats"); + EXPECT_TRUE(pgstat_obj); + } + + { + JSONParser parser; + + ASSERT_TRUE(parser.parse(std::string_view { outstring })); + + JSONObj *pgstat_obj = parser.find_obj("pg_stats"); + EXPECT_TRUE(pgstat_obj); + } + + { + JSONParser parser; + + buffer::list bl; + bl.append(outstring); + EXPECT_TRUE(parser.parse(bl)); + + JSONObj *pgstat_obj = parser.find_obj("pg_stats"); + EXPECT_TRUE(pgstat_obj); + } + + { + JSONParser parser; + + buffer::list bl; + bl.append(json_input); + + ASSERT_TRUE(parser.parse(bl)); + + JSONObjIter oi = parser.find_first(); + + JSONObj *o0 = *oi; + ASSERT_TRUE(nullptr != o0); + + ++oi; + JSONObj *o1 = *oi; + ASSERT_TRUE(nullptr != o1); + JSONObj *conditions = parser.find_obj("conditions"); + ASSERT_TRUE(nullptr != conditions); + + JSONObj *expiration = parser.find_obj("expiration"); + ASSERT_TRUE(nullptr != expiration); + } +}