/* vim:set ts=2 sw=2 sts=2 et: */
/**
* \author Marcus Holland-Moritz (github@mhxnet.de)
* \copyright Copyright (c) Marcus Holland-Moritz
*
* This file is part of dwarfs.
*
* dwarfs is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* dwarfs is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with dwarfs. If not, see .
*/
#include
namespace dwarfs::writer {
namespace detail {
void check_json_common(nlohmann::json const& jsn,
std::string_view expected_type, size_t expected_size,
std::string_view name) {
if (!jsn.is_array()) {
throw std::runtime_error(
fmt::format("found non-array type for requirement '{}', got type '{}'",
name, jsn.type_name()));
}
if (jsn.empty()) {
throw std::runtime_error(
fmt::format("unexpected empty value for requirement '{}'", name));
}
if (!jsn[0].is_string()) {
throw std::runtime_error(
fmt::format("non-string type for requirement '{}', got type '{}'", name,
jsn[0].type_name()));
}
if (auto type = jsn[0].get(); type != expected_type) {
throw std::runtime_error(
fmt::format("invalid type '{}' for requirement '{}', expected '{}'",
type, name, expected_type));
}
if (jsn.size() != expected_size) {
throw std::runtime_error(fmt::format(
"unexpected array size {} for requirement '{}', expected {}",
jsn.size(), name, expected_size));
}
}
void check_unsupported_metadata_requirements(nlohmann::json& req) {
if (!req.empty()) {
std::vector keys;
for (auto const& [k, v] : req.items()) {
keys.emplace_back(k);
}
std::sort(keys.begin(), keys.end());
throw std::runtime_error(fmt::format(
"unsupported metadata requirements: {}", fmt::join(keys, ", ")));
}
}
template
class dynamic_metadata_requirement_set
: public dynamic_metadata_requirement_base {
public:
static_assert(std::is_same_v || std::is_integral_v);
dynamic_metadata_requirement_set(std::string const& name,
nlohmann::json const& req)
: dynamic_metadata_requirement_base{name} {
auto tmp = req;
if (!parse_metadata_requirements_set(set_, tmp, name,
detail::value_parser)) {
throw std::runtime_error(
fmt::format("could not parse set requirement '{}'", name));
}
}
void check(nlohmann::json const& jsn) const override {
if constexpr (std::is_same_v) {
if (!jsn.is_string()) {
throw std::runtime_error(
fmt::format("non-string type for requirement '{}', got type '{}'",
name(), jsn.type_name()));
}
if (set_.find(jsn.get()) == set_.end()) {
throw std::runtime_error(fmt::format(
"{} '{}' does not meet requirements [{}]", name(),
jsn.get(), fmt::join(ordered_set(set_), ", ")));
}
} else {
if (!jsn.is_number_integer()) {
throw std::runtime_error(
fmt::format("non-integral type for requirement '{}', got type '{}'",
name(), jsn.type_name()));
}
if (set_.find(jsn.get()) == set_.end()) {
throw std::runtime_error(
fmt::format("{} '{}' does not meet requirements [{}]", name(),
jsn.get(), fmt::join(ordered_set(set_), ", ")));
}
}
}
private:
std::unordered_set set_;
};
class dynamic_metadata_requirement_range
: public dynamic_metadata_requirement_base {
public:
dynamic_metadata_requirement_range(std::string const& name,
nlohmann::json const& req)
: dynamic_metadata_requirement_base{name} {
auto tmp = req;
if (!parse_metadata_requirements_range(min_, max_, tmp, name,
detail::value_parser)) {
throw std::runtime_error(
fmt::format("could not parse range requirement '{}'", name));
}
}
void check(nlohmann::json const& jsn) const override {
if (!jsn.is_number_integer()) {
throw std::runtime_error(
fmt::format("non-integral type for requirement '{}', got type '{}'",
name(), jsn.type_name()));
}
auto v = jsn.get();
if (v < min_ || v > max_) {
throw std::runtime_error(
fmt::format("{} '{}' does not meet requirements [{}, {}]", name(), v,
min_, max_));
}
}
private:
int64_t min_, max_;
};
} // namespace detail
compression_metadata_requirements<
nlohmann::json>::compression_metadata_requirements(std::string const& req)
: compression_metadata_requirements(nlohmann::json::parse(req)) {}
compression_metadata_requirements::
compression_metadata_requirements(nlohmann::json const& req) {
if (!req.is_object()) {
throw std::runtime_error(
fmt::format("metadata requirements must be an object, got type '{}'",
req.type_name()));
}
for (auto const& [k, v] : req.items()) {
if (!v.is_array()) {
throw std::runtime_error(
fmt::format("requirement '{}' must be an array, got type '{}'", k,
v.type_name()));
}
if (v.size() < 2) {
throw std::runtime_error(
fmt::format("requirement '{}' must be an array of at least 2 "
"elements, got only {}",
k, v.size()));
}
if (!v[0].is_string()) {
throw std::runtime_error(fmt::format(
"type for requirement '{}' must be a string, got type '{}'", k,
v[0].type_name()));
}
if (v[0].get() == "set") {
if (!v[1].is_array()) {
throw std::runtime_error(fmt::format(
"set for requirement '{}' must be an array, got type '{}'", k,
v[1].type_name()));
}
if (v[1].empty()) {
throw std::runtime_error(
fmt::format("set for requirement '{}' must not be empty", k));
}
if (v[1][0].is_string()) {
req_.emplace_back(
std::make_unique<
detail::dynamic_metadata_requirement_set>(k, req));
} else {
req_.emplace_back(
std::make_unique>(
k, req));
}
} else if (v[0].get() == "range") {
req_.emplace_back(
std::make_unique(k, req));
} else {
throw std::runtime_error(
fmt::format("unsupported requirement type {}", v[0].dump()));
}
}
}
void compression_metadata_requirements::check(
nlohmann::json const& jsn) const {
for (auto const& r : req_) {
if (auto it = jsn.find(r->name()); it != jsn.end()) {
r->check(*it);
} else {
throw std::runtime_error(
fmt::format("missing requirement '{}'", r->name()));
}
}
}
void compression_metadata_requirements::check(
std::string const& metadata) const {
check(nlohmann::json::parse(metadata));
}
void compression_metadata_requirements::check(
std::optional const& metadata) const {
nlohmann::json obj;
if (metadata) {
obj = nlohmann::json::parse(*metadata);
}
check(obj);
}
} // namespace dwarfs::writer